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