1# Copyright 2016 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""" 6Repair actions and verifiers relating to CrOS firmware. 7 8This contains the repair actions and verifiers need to find problems 9with the firmware installed on Chrome OS DUTs, and when necessary, to 10fix problems by updating or re-installing the firmware. 11 12The operations in the module support two distinct use cases: 13 * DUTs used for FAFT tests can in some cases have problems with 14 corrupted firmware. The module supplies `FirmwareStatusVerifier` 15 to check for corruption, and supplies `FirmwareRepair` to re-install 16 firmware via servo when needed. 17 * DUTs used for general testing normally should be running a 18 designated "stable" firmware version. This module supplies 19 `FirmwareVersionVerifier` to detect and automatically update 20 firmware that is out-of-date from the designated version. 21 22For purposes of the operations in the module, we distinguish three kinds 23of DUT, based on pool assignments: 24 * DUTs used for general testing. These DUTs automatically check for 25 and install the stable firmware using `FirmwareVersionVerifier`. 26 * DUTs in pools used for FAFT testing. These check for bad firmware 27 builds with `FirmwareStatusVerifier`, and will fix problems using 28 `FirmwareRepair`. These DUTs don't check for or install the 29 stable firmware. 30 * DUTs not in general pools, and not used for FAFT. These DUTs 31 are expected to be managed by separate processes and are excluded 32 from all of the verification and repair code in this module. 33""" 34 35import logging 36import re 37 38import common 39from autotest_lib.client.common_lib import global_config 40from autotest_lib.client.common_lib import hosts 41from autotest_lib.server import afe_utils 42from autotest_lib.site_utils.suite_scheduler import constants 43 44 45# _FIRMWARE_REPAIR_POOLS - The set of pools that should be 46# managed by `FirmwareStatusVerifier` and `FirmwareRepair`. 47# 48_FIRMWARE_REPAIR_POOLS = set( 49 global_config.global_config.get_config_value( 50 'CROS', 51 'pools_support_firmware_repair', 52 type=str).split(',')) 53 54 55# _FIRMWARE_UPDATE_POOLS - The set of pools that should be 56# managed by `FirmwareVersionVerifier`. 57# 58_FIRMWARE_UPDATE_POOLS = set(constants.Pools.MANAGED_POOLS) 59 60 61def _is_firmware_repair_supported(host): 62 """ 63 Check if a host supports firmware repair. 64 65 When this function returns true, the DUT should be managed by 66 `FirmwareStatusVerifier` and `FirmwareRepair`, but not 67 `FirmwareVersionVerifier`. In general, this applies to DUTs 68 used for firmware testing. 69 70 @return A true value if the host should use `FirmwareStatusVerifier` 71 and `FirmwareRepair`; a false value otherwise. 72 """ 73 info = host.host_info_store.get() 74 return bool(info.pools & _FIRMWARE_REPAIR_POOLS) 75 76 77def _is_firmware_update_supported(host): 78 """ 79 Return whether a DUT should be running the standard firmware. 80 81 In the test lab, DUTs used for general testing, (e.g. the `bvt` 82 pool) need their firmware kept up-to-date with 83 `FirmwareVersionVerifier`. However, some pools have alternative 84 policies for firmware management. This returns whether a given DUT 85 should be updated via the standard stable version update, or 86 managed by some other procedure. 87 88 @param host The host to be checked for update policy. 89 @return A true value if the host should use 90 `FirmwareVersionVerifier`; a false value otherwise. 91 """ 92 info = host.host_info_store.get() 93 return bool(info.pools & _FIRMWARE_UPDATE_POOLS) 94 95 96class FirmwareStatusVerifier(hosts.Verifier): 97 """ 98 Verify that a host's firmware is in a good state. 99 100 For DUTs that run firmware tests, it's possible that the firmware 101 on the DUT can get corrupted. This verifier checks whether it 102 appears that firmware should be re-flashed using servo. 103 """ 104 105 def verify(self, host): 106 if not _is_firmware_repair_supported(host): 107 return 108 try: 109 # Read the AP firmware and dump the sections that we're 110 # interested in. 111 cmd = ('mkdir /tmp/verify_firmware; ' 112 'cd /tmp/verify_firmware; ' 113 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; ' 114 'do flashrom -r image.bin -i $section:$section; ' 115 'done') 116 host.run(cmd) 117 118 # Verify the firmware blocks A and B. 119 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c' 120 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk' 121 ' --fv /tmp/verify_firmware/FW_MAIN_%c') 122 for c in ('A', 'B'): 123 rv = host.run(cmd % (c, c), ignore_status=True) 124 if rv.exit_status: 125 raise hosts.AutoservVerifyError( 126 'Firmware %c is in a bad state.' % c) 127 finally: 128 # Remove the temporary files. 129 host.run('rm -rf /tmp/verify_firmware') 130 131 @property 132 def description(self): 133 return 'Firmware on this DUT is clean' 134 135 136class FirmwareRepair(hosts.RepairAction): 137 """ 138 Reinstall the firmware image using servo. 139 140 This repair function attempts to use servo to install the DUT's 141 designated "stable firmware version". 142 143 This repair method only applies to DUTs used for FAFT. 144 """ 145 146 def repair(self, host): 147 if not _is_firmware_repair_supported(host): 148 raise hosts.AutoservRepairError( 149 'Firmware repair is not applicable to host %s.' % 150 host.hostname) 151 if not host.servo: 152 raise hosts.AutoservRepairError( 153 '%s has no servo support.' % host.hostname) 154 host.firmware_install() 155 156 @property 157 def description(self): 158 return 'Re-install the stable firmware via servo' 159 160 161class FirmwareVersionVerifier(hosts.Verifier): 162 """ 163 Check for a firmware update, and apply it if appropriate. 164 165 This verifier checks to ensure that either the firmware on the DUT 166 is up-to-date, or that the target firmware can be installed from the 167 currently running build. 168 169 Failure occurs when all of the following apply: 170 1. The DUT is not excluded from updates. For example, DUTs used 171 for FAFT testing use `FirmwareRepair` instead. 172 2. The DUT's board has an assigned stable firmware version. 173 3. The DUT is not running the assigned stable firmware. 174 4. The firmware supplied in the running OS build is not the 175 assigned stable firmware. 176 177 If the DUT needs an upgrade and the currently running OS build 178 supplies the necessary firmware, the verifier installs the new 179 firmware using `chromeos-firmwareupdate`. Failure to install will 180 cause the verifier to fail. 181 182 This verifier nominally breaks the rule that "verifiers must succeed 183 quickly", since it can invoke `reboot()` during the success code 184 path. We're doing it anyway for two reasons: 185 * The time between updates will typically be measured in months, 186 so the amortized cost is low. 187 * The reason we distinguish repair from verify is to allow 188 rescheduling work immediately while the expensive repair happens 189 out-of-band. But a firmware update will likely hit all DUTs at 190 once, so it's pointless to pass the buck to repair. 191 192 N.B. This verifier is a trigger for all repair actions that install 193 the stable repair image. If the firmware is out-of-date, but the 194 stable repair image does *not* contain the proper firmware version, 195 _the target DUT will fail repair, and will be unable to fix itself_. 196 """ 197 198 @staticmethod 199 def _get_rw_firmware(host): 200 result = host.run('crossystem fwid', ignore_status=True) 201 if result.exit_status == 0: 202 return result.stdout 203 else: 204 return None 205 206 @staticmethod 207 def _get_available_firmware(host): 208 result = host.run('chromeos-firmwareupdate -V', 209 ignore_status=True) 210 if result.exit_status == 0: 211 version = re.search(r'BIOS version:\s*(?P<version>.*)', 212 result.stdout) 213 if version is not None: 214 return version.group('version') 215 return None 216 217 @staticmethod 218 def _check_hardware_match(version_a, version_b): 219 """ 220 Check that two firmware versions identify the same hardware. 221 222 Firmware version strings look like this: 223 Google_Gnawty.5216.239.34 224 The part before the numbers identifies the hardware for which 225 the firmware was built. This function checks that the hardware 226 identified by `version_a` and `version_b` is the same. 227 228 This is a sanity check to protect us from installing the wrong 229 firmware on a DUT when a board label has somehow gone astray. 230 231 @param version_a First firmware version for the comparison. 232 @param version_b Second firmware version for the comparison. 233 """ 234 hardware_a = version_a.split('.')[0] 235 hardware_b = version_b.split('.')[0] 236 if hardware_a != hardware_b: 237 message = 'Hardware/Firmware mismatch updating %s to %s' 238 raise hosts.AutoservVerifyError( 239 message % (version_a, version_b)) 240 241 def verify(self, host): 242 # Test 1 - The DUT is not excluded from updates. 243 if not _is_firmware_update_supported(host): 244 return 245 # Test 2 - The DUT has an assigned stable firmware version. 246 info = host.host_info_store.get() 247 if info.board is None: 248 raise hosts.AutoservVerifyError( 249 'Can not verify firmware version. ' 250 'No board label value found') 251 252 stable_firmware = afe_utils.get_stable_firmware_version(info.board) 253 if stable_firmware is None: 254 # This DUT doesn't have a firmware update target 255 return 256 257 # For tests 3 and 4: If the output from `crossystem` or 258 # `chromeos-firmwareupdate` isn't what we expect, we log an 259 # error, but don't fail: We don't want DUTs unable to test a 260 # build merely because of a bug or change in either of those 261 # commands. 262 263 # Test 3 - The DUT is not running the target stable firmware. 264 current_firmware = self._get_rw_firmware(host) 265 if current_firmware is None: 266 logging.error('DUT firmware version can\'t be determined.') 267 return 268 if current_firmware == stable_firmware: 269 return 270 # Test 4 - The firmware supplied in the running OS build is not 271 # the assigned stable firmware. 272 available_firmware = self._get_available_firmware(host) 273 if available_firmware is None: 274 logging.error('Supplied firmware version in OS can\'t be ' 275 'determined.') 276 return 277 if available_firmware != stable_firmware: 278 raise hosts.AutoservVerifyError( 279 'DUT firmware requires update from %s to %s' % 280 (current_firmware, stable_firmware)) 281 # Time to update the firmware. 282 logging.info('Updating firmware from %s to %s', 283 current_firmware, stable_firmware) 284 self._check_hardware_match(current_firmware, stable_firmware) 285 try: 286 host.run('chromeos-firmwareupdate --mode=autoupdate') 287 host.reboot() 288 except Exception as e: 289 message = ('chromeos-firmwareupdate failed: from ' 290 '%s to %s') 291 logging.exception(message, current_firmware, stable_firmware) 292 raise hosts.AutoservVerifyError( 293 message % (current_firmware, stable_firmware)) 294 final_firmware = self._get_rw_firmware(host) 295 if final_firmware != stable_firmware: 296 message = ('chromeos-firmwareupdate failed: tried upgrade ' 297 'to %s, now running %s instead') 298 raise hosts.AutoservVerifyError( 299 message % (stable_firmware, final_firmware)) 300 301 @property 302 def description(self): 303 return 'The firmware on this DUT is up-to-date' 304