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