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