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 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib.cros import cr50_utils
     10 from autotest_lib.server.cros.faft.cr50_test import Cr50Test
     11 
     12 
     13 class firmware_Cr50Update(Cr50Test):
     14     """
     15     Verify a dut can update to the given image.
     16 
     17     Copy the new image onto the device and clear the update state to force
     18     cr50-update to run. The test will fail if Cr50 does not update or if the
     19     update script encounters any errors.
     20 
     21     @param image: the location of the update image
     22     @param image_type: string representing the image type. If it is "dev" then
     23                        don't check the RO versions when comparing versions.
     24     """
     25     version = 1
     26     UPDATE_TIMEOUT = 20
     27     ERASE_NVMEM = "erase_nvmem"
     28 
     29     DEV_NAME = "dev_image"
     30     OLD_RELEASE_NAME = "old_release_image"
     31     RELEASE_NAME = "release_image"
     32     SUCCESS = 0
     33     UPDATE_OK = 1
     34 
     35 
     36     def initialize(self, host, cmdline_args, release_path="", release_ver="",
     37                    old_release_path="", old_release_ver="", dev_path="",
     38                    test=""):
     39         """Initialize servo and process the given images"""
     40         super(firmware_Cr50Update, self).initialize(host, cmdline_args,
     41                                                     restore_cr50_state=True,
     42                                                     cr50_dev_path=dev_path)
     43 
     44         if not release_ver and not os.path.isfile(release_path):
     45             raise error.TestError('Need to specify a release version or path')
     46 
     47         self.devid = self.servo.get('cr50_devid')
     48 
     49         # Make sure ccd is disabled so it won't interfere with the update
     50         self.cr50.ccd_disable()
     51 
     52         self.rootfs_verification_disable()
     53 
     54         self.host = host
     55         self.erase_nvmem = test.lower() == self.ERASE_NVMEM
     56 
     57         # A dict used to store relevant information for each image
     58         self.images = {}
     59 
     60         # Process the given images in order of oldest to newest. Get the version
     61         # info and add them to the update order
     62         self.update_order = []
     63         if not self.erase_nvmem and (old_release_path or old_release_ver):
     64             self.add_image_to_update_order(self.OLD_RELEASE_NAME,
     65                                            old_release_path, old_release_ver)
     66         self.add_image_to_update_order(self.RELEASE_NAME, release_path,
     67                                        release_ver)
     68         self.add_image_to_update_order(self.DEV_NAME, dev_path)
     69         self.verify_update_order()
     70         logging.info("Update %s", self.update_order)
     71 
     72         self.chip_bid = None
     73         self.chip_flags = None
     74         chip_bid_info = cr50_utils.GetChipBoardId(self.host)
     75         if chip_bid_info != cr50_utils.ERASED_CHIP_BID:
     76             self.chip_bid, _, self.chip_flags = chip_bid_info
     77             logging.info('chip board id will be erased during rollback. %x:%x '
     78                 'will be restored after rollback.',  self.chip_bid,
     79                 self.chip_flags)
     80         else:
     81             logging.info('No chip board id is set. This test will not attempt '
     82                 'to restore anything during rollback.')
     83 
     84         self.device_update_path = cr50_utils.GetActiveCr50ImagePath(self.host)
     85         # Update to the dev image
     86         self.run_update(self.DEV_NAME)
     87 
     88     def cleanup(self):
     89         """Update Cr50 to the image it was running at the start of the test"""
     90         super(firmware_Cr50Update, self).cleanup()
     91 
     92         logging.warning('rootfs verification is disabled')
     93 
     94         # Make sure keepalive is disabled
     95         self.cr50.ccd_enable()
     96         self.cr50.send_command("ccd keepalive disable")
     97 
     98         # Running usb_update commands stops trunksd. Reboot the device to reset
     99         # it
    100         self.host.run('reboot')
    101 
    102 
    103     def run_update(self, image_name, use_usb_update=False):
    104         """Copy the image to the DUT and upate to it.
    105 
    106         Normal updates will use the cr50-update script to update. If a rollback
    107         is True, use usb_update to flash the image and then use the 'rw'
    108         commands to force a rollback.
    109 
    110         @param image_name: the key in the images dict that can be used to
    111                            retrieve the image info.
    112         @param use_usb_update: True if usb_updater should be used directly
    113                                instead of running the update script.
    114         """
    115         self.cr50.ccd_disable()
    116         # Get the current update information
    117         image_ver, image_ver_str, image_path = self.images[image_name]
    118 
    119         dest, ver = cr50_utils.InstallImage(self.host, image_path,
    120                 self.device_update_path)
    121         assert ver == image_ver, "Install failed"
    122         image_rw = image_ver[1]
    123 
    124         running_ver = cr50_utils.GetRunningVersion(self.host)
    125         running_ver_str = cr50_utils.GetVersionString(running_ver)
    126 
    127         # If the given image is older than the running one, then we will need
    128         # to do a rollback to complete the update.
    129         rollback = (cr50_utils.GetNewestVersion(running_ver[1], image_rw) !=
    130                     image_rw)
    131         logging.info("Attempting %s from %s to %s",
    132                      "rollback" if rollback else "update", running_ver_str,
    133                      image_ver_str)
    134 
    135         # If a rollback is needed, flash the image into the inactive partition,
    136         # on or use usb_update to update to the new image if it is requested.
    137         if use_usb_update or rollback:
    138             self.cr50_update(image_path, rollback=rollback,
    139                 chip_bid=self.chip_bid, chip_flags=self.chip_flags,
    140                 erase_nvmem=self.erase_nvmem)
    141             self.check_state((self.checkers.crossystem_checker,
    142                               {'mainfw_type': 'normal'}))
    143 
    144         # Cr50 is going to reject an update if it hasn't been up for more than
    145         # 60 seconds. Wait until that passes before trying to run the update.
    146         self.cr50.wait_until_update_is_allowed()
    147 
    148         # Running the usb update or rollback will enable ccd. Disable it again.
    149         self.cr50.ccd_disable()
    150 
    151         # Get the last cr50 update related message from /var/log/messages
    152         last_message = cr50_utils.CheckForFailures(self.host, '')
    153 
    154         # Clear the update state and reboot, so cr50-update will run again.
    155         cr50_utils.ClearUpdateStateAndReboot(self.host)
    156 
    157         # The cr50 updates happen over /dev/tpm0. It takes a while. After
    158         # cr50-update has finished, cr50 should reboot. Wait until this happens
    159         # before sending anymore commands.
    160         self.cr50.wait_for_reboot()
    161 
    162         # Verify the system boots normally after the update
    163         self.check_state((self.checkers.crossystem_checker,
    164                           {'mainfw_type': 'normal'}))
    165 
    166         # Verify the version has been updated and that there have been no
    167         # unexpected usb_updater exit codes.
    168         cr50_utils.VerifyUpdate(self.host, image_ver, last_message)
    169 
    170         logging.info("Successfully updated from %s to %s %s", running_ver_str,
    171                      image_name, image_ver_str)
    172 
    173 
    174     def fetch_image(self, ver=None):
    175         """Fetch the image from gs and copy it to the host.
    176 
    177         @param ver: The rw version of the prod image. If it is not None then the
    178                     image will be retrieved from chromeos-localmirror otherwise
    179                     it will be gotten from chromeos-localmirror-private using
    180                     the devids
    181         """
    182         if ver:
    183             bid = None
    184             if '/' in ver:
    185                 ver, bid = ver.split('/', 1)
    186             return self.download_cr50_release_image(ver, bid)
    187         return self.download_cr50_debug_image(self.devid)
    188 
    189 
    190     def add_image_to_update_order(self, image_name, image_path, ver=None):
    191         """Process the image. Add it to the update_order list and images dict.
    192 
    193         Copy the image to the DUT and get version information.
    194 
    195         Store the image information in the images dictionary and add it to the
    196         update_order list.
    197 
    198         @param image_name: string that is what the image should be called. Used
    199                            as the key in the images dict.
    200         @param image_path: the path for the image.
    201         @param ver: If the image path isn't specified, this will be used to find
    202                     the cr50 image in gs://chromeos-localmirror/distfiles.
    203         """
    204         tmp_file = '/tmp/%s.bin' % image_name
    205 
    206         if not os.path.isfile(image_path):
    207             image_path, ver = self.fetch_image(ver)
    208         else:
    209             _, ver = cr50_utils.InstallImage(self.host, image_path, tmp_file)
    210 
    211         ver_str = cr50_utils.GetVersionString(ver)
    212 
    213         self.update_order.append(image_name)
    214         self.images[image_name] = (ver, ver_str, image_path)
    215         logging.info("%s stored at %s with version %s", image_name, image_path,
    216                      ver_str)
    217 
    218 
    219     def verify_update_order(self):
    220         """Verify each image in the update order has a higher version than the
    221         last.
    222 
    223         The test uses the cr50 update script to update to the next image in the
    224         update order. If the versions are not in ascending order then the update
    225         won't work. Cr50 cannot update to an older version using the standard
    226         update process.
    227 
    228         Raises:
    229             TestError if the versions are not in ascending order.
    230         """
    231         for i, name in enumerate(self.update_order[1::]):
    232             rw = self.images[name][0][1]
    233 
    234             last_name = self.update_order[i]
    235             last_rw = self.images[last_name][0][1]
    236             if cr50_utils.GetNewestVersion(last_rw, rw) != rw:
    237                 raise error.TestError("%s is version %s. %s needs to have a "
    238                                       "higher version, but it has %s" %
    239                                       (last_name, last_rw, name, rw))
    240 
    241 
    242     def after_run_once(self):
    243         """Add log printing what iteration we just completed"""
    244         logging.info("Update iteration %s ran successfully", self.iteration)
    245 
    246 
    247     def run_once(self):
    248         """Update to each image in update_order"""
    249         for name in self.update_order:
    250             self.run_update(name)
    251