blob: 6e51f98c94ec4bc98cde78ef8e84b6963e2c54bf [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
Richard Barnette1bf22a32016-11-18 16:14:31 -080061def _is_firmware_repair_supported(host):
62 """
63 Check if a host supports firmware repair.
64
Richard Barnette077665e2016-11-29 16:00:59 -080065 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.
Richard Barnette1bf22a32016-11-18 16:14:31 -080069
Richard Barnette077665e2016-11-29 16:00:59 -080070 @return A true value if the host should use `FirmwareStatusVerifier`
71 and `FirmwareRepair`; a false value otherwise.
Richard Barnette1bf22a32016-11-18 16:14:31 -080072 """
Prathmesh Prabhub6cea612017-02-09 15:41:19 -080073 info = host.host_info_store.get()
74 return bool(info.pools & _FIRMWARE_REPAIR_POOLS)
Richard Barnette077665e2016-11-29 16:00:59 -080075
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 """
Prathmesh Prabhub6cea612017-02-09 15:41:19 -080092 info = host.host_info_store.get()
93 return bool(info.pools & _FIRMWARE_UPDATE_POOLS)
Richard Barnette1bf22a32016-11-18 16:14:31 -080094
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'
Richard Barnette90ad4262016-11-17 17:29:24 -0800134
135
Richard Barnette077665e2016-11-29 16:00:59 -0800136class 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
Richard Barnette90ad4262016-11-17 17:29:24 -0800161class 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:
Richard Barnette077665e2016-11-29 16:00:59 -0800170 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.
Richard Barnette90ad4262016-11-17 17:29:24 -0800173 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
Richard Barnette077665e2016-11-29 16:00:59 -0800178 supplies the necessary firmware, the verifier installs the new
179 firmware using `chromeos-firmwareupdate`. Failure to install will
180 cause the verifier to fail.
Richard Barnette90ad4262016-11-17 17:29:24 -0800181
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:
Aseda Aboagyef45b3c72017-04-26 12:53:24 -0700211 # At one point, the chromeos-firmwareupdate script was updated to
212 # add "RW" version fields. The old string, "BIOS version:" still
213 # appears in the new output, however it now refers to the RO
214 # firmware version. Therefore, we try searching for the new string
215 # first, "BIOS (RW) version". If that string isn't found, we then
216 # fallback to searching for old string.
217 version = re.search(r'BIOS \(RW\) version:\s*(?P<version>.*)',
Richard Barnette90ad4262016-11-17 17:29:24 -0800218 result.stdout)
Aseda Aboagyef45b3c72017-04-26 12:53:24 -0700219 if not version:
220 version = re.search(r'BIOS version:\s*(?P<version>.*)',
221 result.stdout)
Richard Barnette90ad4262016-11-17 17:29:24 -0800222 if version is not None:
223 return version.group('version')
224 return None
225
226 @staticmethod
227 def _check_hardware_match(version_a, version_b):
228 """
229 Check that two firmware versions identify the same hardware.
230
231 Firmware version strings look like this:
232 Google_Gnawty.5216.239.34
233 The part before the numbers identifies the hardware for which
234 the firmware was built. This function checks that the hardware
235 identified by `version_a` and `version_b` is the same.
236
237 This is a sanity check to protect us from installing the wrong
238 firmware on a DUT when a board label has somehow gone astray.
239
240 @param version_a First firmware version for the comparison.
241 @param version_b Second firmware version for the comparison.
242 """
243 hardware_a = version_a.split('.')[0]
244 hardware_b = version_b.split('.')[0]
245 if hardware_a != hardware_b:
246 message = 'Hardware/Firmware mismatch updating %s to %s'
247 raise hosts.AutoservVerifyError(
248 message % (version_a, version_b))
249
250 def verify(self, host):
Richard Barnette077665e2016-11-29 16:00:59 -0800251 # Test 1 - The DUT is not excluded from updates.
252 if not _is_firmware_update_supported(host):
Richard Barnette90ad4262016-11-17 17:29:24 -0800253 return
254 # Test 2 - The DUT has an assigned stable firmware version.
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800255 info = host.host_info_store.get()
256 if info.board is None:
257 raise hosts.AutoservVerifyError(
258 'Can not verify firmware version. '
259 'No board label value found')
260
261 stable_firmware = afe_utils.get_stable_firmware_version(info.board)
Richard Barnette90ad4262016-11-17 17:29:24 -0800262 if stable_firmware is None:
263 # This DUT doesn't have a firmware update target
264 return
265
266 # For tests 3 and 4: If the output from `crossystem` or
267 # `chromeos-firmwareupdate` isn't what we expect, we log an
268 # error, but don't fail: We don't want DUTs unable to test a
269 # build merely because of a bug or change in either of those
270 # commands.
271
272 # Test 3 - The DUT is not running the target stable firmware.
273 current_firmware = self._get_rw_firmware(host)
274 if current_firmware is None:
275 logging.error('DUT firmware version can\'t be determined.')
276 return
277 if current_firmware == stable_firmware:
278 return
279 # Test 4 - The firmware supplied in the running OS build is not
280 # the assigned stable firmware.
281 available_firmware = self._get_available_firmware(host)
282 if available_firmware is None:
283 logging.error('Supplied firmware version in OS can\'t be '
284 'determined.')
285 return
286 if available_firmware != stable_firmware:
287 raise hosts.AutoservVerifyError(
288 'DUT firmware requires update from %s to %s' %
289 (current_firmware, stable_firmware))
290 # Time to update the firmware.
291 logging.info('Updating firmware from %s to %s',
292 current_firmware, stable_firmware)
293 self._check_hardware_match(current_firmware, stable_firmware)
294 try:
295 host.run('chromeos-firmwareupdate --mode=autoupdate')
296 host.reboot()
297 except Exception as e:
298 message = ('chromeos-firmwareupdate failed: from '
299 '%s to %s')
300 logging.exception(message, current_firmware, stable_firmware)
301 raise hosts.AutoservVerifyError(
302 message % (current_firmware, stable_firmware))
Richard Barnette1b489932017-02-14 10:50:58 -0800303 final_firmware = self._get_rw_firmware(host)
304 if final_firmware != stable_firmware:
305 message = ('chromeos-firmwareupdate failed: tried upgrade '
306 'to %s, now running %s instead')
307 raise hosts.AutoservVerifyError(
308 message % (stable_firmware, final_firmware))
Richard Barnette90ad4262016-11-17 17:29:24 -0800309
310 @property
311 def description(self):
312 return 'The firmware on this DUT is up-to-date'