Home | History | Annotate | Download | only in firmware_Cr50Update
      1 # Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import os
      7 import time
      8 
      9 from autotest_lib.client.common_lib import error, utils
     10 from autotest_lib.client.common_lib.cros import cr50_utils, tpm_utils
     11 from autotest_lib.server import autotest, test
     12 from autotest_lib.server.cros import debugd_dev_tools, gsutil_wrapper
     13 from autotest_lib.server.cros.faft.cr50_test import Cr50Test
     14 
     15 
     16 class firmware_Cr50Update(Cr50Test):
     17     """
     18     Verify a dut can update to the given image.
     19 
     20     Copy the new image onto the device and clear the update state to force
     21     cr50-update to run. The test will fail if Cr50 does not update or if the
     22     update script encounters any errors.
     23 
     24     @param image: the location of the update image
     25     @param image_type: string representing the image type. If it is "dev" then
     26                        don't check the RO versions when comparing versions.
     27     """
     28     version = 1
     29     UPDATE_TIMEOUT = 20
     30     ERASE_NVMEM = "erase_nvmem"
     31 
     32     DEV_NAME = "dev_image"
     33     OLD_RELEASE_NAME = "old_release_image"
     34     RELEASE_NAME = "release_image"
     35     ORIGINAL_NAME = "original_image"
     36     RESTORE_ORIGINAL_TRIES = 3
     37     SUCCESS = 0
     38     UPDATE_OK = 1
     39 
     40 
     41     def initialize(self, host, cmdline_args, release_path="", release_ver="",
     42                    old_release_path="", old_release_ver="", dev_path="",
     43                    test=""):
     44         """Initialize servo and process the given images"""
     45         self.processed_images = False
     46 
     47         super(firmware_Cr50Update, self).initialize(host, cmdline_args)
     48         if not hasattr(self, "cr50"):
     49             raise error.TestNAError('Test can only be run on devices with '
     50                                     'access to the Cr50 console')
     51 
     52         if not release_ver and not os.path.isfile(release_path):
     53             raise error.TestError('Need to specify a release version or path')
     54 
     55         self.devid = self.servo.get('cr50_devid')
     56 
     57         # Make sure ccd is disabled so it won't interfere with the update
     58         self.cr50.ccd_disable()
     59 
     60         tpm_utils.ClearTPMOwnerRequest(host)
     61         self.rootfs_tool = debugd_dev_tools.RootfsVerificationTool()
     62         self.rootfs_tool.initialize(host)
     63         if not self.rootfs_tool.is_enabled():
     64             logging.debug('Removing rootfs verification.')
     65             # 'enable' actually disables rootfs verification
     66             self.rootfs_tool.enable()
     67 
     68         self.host = host
     69         self.erase_nvmem = test.lower() == self.ERASE_NVMEM
     70 
     71         # A dict used to store relevant information for each image
     72         self.images = {}
     73 
     74         # Get the original image from the cr50 firmware directory on the dut
     75         self.save_original_image(cr50_utils.CR50_FILE)
     76 
     77         # Process the given images in order of oldest to newest. Get the version
     78         # info and add them to the update order
     79         self.update_order = []
     80         if not self.erase_nvmem and (old_release_path or old_release_ver):
     81             self.add_image_to_update_order(self.OLD_RELEASE_NAME,
     82                                            old_release_path, old_release_ver)
     83         self.add_image_to_update_order(self.RELEASE_NAME, release_path,
     84                                        release_ver)
     85         self.add_image_to_update_order(self.DEV_NAME, dev_path)
     86         self.verify_update_order()
     87         self.processed_images = True
     88         logging.info("Update %s", self.update_order)
     89 
     90         # Update to the dev image
     91         self.run_update(self.DEV_NAME)
     92 
     93 
     94     def restore_original_image(self):
     95         """Update to the image that was running at the start of the test.
     96 
     97         Returns SUCCESS if the update was successful or the update error if it
     98         failed.
     99         """
    100         rv = self.SUCCESS
    101 
    102         original_ver, _, original_path = self.images[self.ORIGINAL_NAME]
    103         original_rw = original_ver[1]
    104         cr50_utils.InstallImage(self.host, original_path)
    105 
    106         _, running_rw, is_dev = self.cr50.get_active_version_info()
    107         new_rw = cr50_utils.GetNewestVersion(running_rw, original_rw)
    108 
    109         # If Cr50 is running the original image, then no update is needed.
    110         if new_rw is None:
    111             return rv
    112 
    113         try:
    114             # If a rollback is needed, update to the dev image so it can
    115             # rollback to the original image.
    116             if new_rw != original_rw and not is_dev:
    117                 logging.info("Updating to dev image to enable rollback")
    118                 self.cr50_update(self.images[self.DEV_NAME][2])
    119 
    120             logging.info("Updating to the original image %s",
    121                          original_rw)
    122             self.cr50_update(original_path, rollback=True)
    123         except Exception, e:
    124             logging.info("cleanup update from %s to %s failed", running_rw,
    125                           original_rw)
    126             logging.debug(e)
    127             rv = e
    128         self.cr50.ccd_enable()
    129         return rv
    130 
    131 
    132     def cleanup(self):
    133         """Update Cr50 to the image it was running at the start of the test"""
    134         logging.warning('rootfs verification is disabled')
    135 
    136         # Make sure keepalive is disabled
    137         self.cr50.ccd_enable()
    138         self.cr50.send_command("ccd keepalive disable")
    139 
    140         # Restore the original Cr50 image
    141         if self.processed_images:
    142             for i in xrange(self.RESTORE_ORIGINAL_TRIES):
    143                 if self.restore_original_image() == self.SUCCESS:
    144                     logging.info("Successfully restored the original image")
    145                     break
    146             else:
    147                 raise error.TestError("Could not restore the original image")
    148 
    149         super(firmware_Cr50Update, self).cleanup()
    150 
    151 
    152     def run_update(self, image_name, use_usb_update=False):
    153         """Copy the image to the DUT and upate to it.
    154 
    155         Normal updates will use the cr50-update script to update. If a rollback
    156         is True, use usb_update to flash the image and then use the 'rw'
    157         commands to force a rollback.
    158 
    159         @param image_name: the key in the images dict that can be used to
    160                            retrieve the image info.
    161         @param use_usb_update: True if usb_updater should be used directly
    162                                instead of running the update script.
    163         """
    164         self.cr50.ccd_disable()
    165         # Get the current update information
    166         image_ver, image_ver_str, image_path = self.images[image_name]
    167 
    168         dest, ver = cr50_utils.InstallImage(self.host, image_path)
    169         assert ver == image_ver, "Install failed"
    170         image_rw = image_ver[1]
    171 
    172         running_ver = cr50_utils.GetRunningVersion(self.host)
    173         running_ver_str = cr50_utils.GetVersionString(running_ver)
    174 
    175         # If the given image is older than the running one, then we will need
    176         # to do a rollback to complete the update.
    177         rollback = (cr50_utils.GetNewestVersion(running_ver[1], image_rw) !=
    178                     image_rw)
    179         logging.info("Attempting %s from %s to %s",
    180                      "rollback" if rollback else "update", running_ver_str,
    181                      image_ver_str)
    182 
    183         # If a rollback is needed, flash the image into the inactive partition,
    184         # on or use usb_update to update to the new image if it is requested.
    185         if use_usb_update or rollback:
    186             self.cr50_update(dest, rollback=rollback,
    187                              erase_nvmem=self.erase_nvmem)
    188             self.check_state((self.checkers.crossystem_checker,
    189                               {'mainfw_type': 'normal'}))
    190 
    191         # Running the usb update or rollback will enable ccd. Disable it again.
    192         self.cr50.ccd_disable()
    193 
    194         # Get the last cr50 update related message from /var/log/messages
    195         last_message = cr50_utils.CheckForFailures(self.host, '')
    196 
    197         # Clear the update state and reboot, so cr50-update will run again.
    198         cr50_utils.ClearUpdateStateAndReboot(self.host)
    199 
    200         # Verify the system boots normally after the update
    201         self.check_state((self.checkers.crossystem_checker,
    202                           {'mainfw_type': 'normal'}))
    203 
    204         # Verify the version has been updated and that there have been no
    205         # unexpected usb_updater exit codes.
    206         cr50_utils.VerifyUpdate(self.host, image_ver, last_message)
    207 
    208         logging.info("Successfully updated from %s to %s %s", running_ver_str,
    209                      image_name, image_ver_str)
    210 
    211 
    212     def fetch_image(self, ver=None):
    213         """Fetch the image from gs and copy it to the host.
    214 
    215         @param ver: The rw version of the prod image. If it is not None then the
    216                     image will be retrieved from chromeos-localmirror otherwise
    217                     it will be gotten from chromeos-localmirror-private using
    218                     the devids
    219         """
    220         if ver:
    221             return self.download_cr50_release_image(ver)
    222         return self.download_cr50_debug_image(self.devid)
    223 
    224 
    225     def add_image_to_update_order(self, image_name, image_path, ver=None):
    226         """Process the image. Add it to the update_order list and images dict.
    227 
    228         Copy the image to the DUT and get version information.
    229 
    230         Store the image information in the images dictionary and add it to the
    231         update_order list.
    232 
    233         @param image_name: string that is what the image should be called. Used
    234                            as the key in the images dict.
    235         @param image_path: the path for the image.
    236         @param ver: If the image path isn't specified, this will be used to find
    237                     the cr50 image in gs://chromeos-localmirror/distfiles.
    238         """
    239         tmp_file = '/tmp/%s.bin' % image_name
    240 
    241         if not os.path.isfile(image_path):
    242             image_path = self.fetch_image(ver)
    243 
    244         _, ver = cr50_utils.InstallImage(self.host, image_path, tmp_file)
    245 
    246         ver_str = cr50_utils.GetVersionString(ver)
    247 
    248         self.update_order.append(image_name)
    249         self.images[image_name] = (ver, ver_str, image_path)
    250         logging.info("%s stored at %s with version %s", image_name, image_path,
    251                      ver_str)
    252 
    253 
    254     def verify_update_order(self):
    255         """Verify each image in the update order has a higher version than the
    256         last.
    257 
    258         The test uses the cr50 update script to update to the next image in the
    259         update order. If the versions are not in ascending order then the update
    260         won't work. Cr50 cannot update to an older version using the standard
    261         update process.
    262 
    263         Raises:
    264             TestError if the versions are not in ascending order.
    265         """
    266         for i, name in enumerate(self.update_order[1::]):
    267             rw = self.images[name][0][1]
    268 
    269             last_name = self.update_order[i]
    270             last_rw = self.images[last_name][0][1]
    271             if cr50_utils.GetNewestVersion(last_rw, rw) != rw:
    272                 raise error.TestError("%s is version %s. %s needs to have a "
    273                                       "higher version, but it has %s" %
    274                                       (last_name, last_rw, name, rw))
    275 
    276 
    277     def save_original_image(self, dut_path):
    278         """Save the image currently running on the DUT.
    279 
    280         Copy the image from the DUT to the local autotest directory and get
    281         version information. Store the information in the images dict. Make sure
    282         the saved version matches the running version.
    283 
    284         Args:
    285             dut_path: the location of the cr50 prod image on the DUT.
    286 
    287         Raises:
    288             error.TestError if the saved cr50 image version does not match the
    289             version cr50 is running.
    290         """
    291         name = self.ORIGINAL_NAME
    292         local_dest = os.path.join(self.resultsdir, name + '.bin')
    293 
    294         running_ver = cr50_utils.GetRunningVersion(self.host)
    295         running_ver_str = cr50_utils.GetVersionString(running_ver)
    296 
    297         self.host.get_file(dut_path, local_dest)
    298 
    299         saved_ver = cr50_utils.GetBinVersion(self.host, dut_path)
    300         saved_ver_str = cr50_utils.GetVersionString(saved_ver)
    301 
    302         # If Cr50 is not running the image in the cr50 firmware directory, then
    303         # raise an error. We can't run this test unless we can restore the
    304         # original state during cleanup.
    305         if running_ver[1] != saved_ver[1]:
    306             raise error.TestError("Can't determine original Cr50 version. "
    307                                   "Running %s, but saved %s." %
    308                                   (running_ver_str, saved_ver_str))
    309 
    310         self.images[name] = (saved_ver, saved_ver_str, local_dest)
    311         logging.info("%s stored at %s with version %s", name, local_dest,
    312                      saved_ver_str)
    313 
    314 
    315     def after_run_once(self):
    316         """Add log printing what iteration we just completed"""
    317         logging.info("Update iteration %s ran successfully", self.iteration)
    318 
    319 
    320     def run_once(self):
    321         for name in self.update_order:
    322             self.run_update(name)
    323