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