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