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