blob: 741152e3c2bf48479300a3e5515dfed418eb32af [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
Richard Barnette1bf22a32016-11-18 16:14:31 -080044
45
Richard Barnette077665e2016-11-29 16:00:59 -080046# _FIRMWARE_REPAIR_POOLS - The set of pools that should be
47# managed by `FirmwareStatusVerifier` and `FirmwareRepair`.
48#
49_FIRMWARE_REPAIR_POOLS = set(
50 global_config.global_config.get_config_value(
51 'CROS',
52 'pools_support_firmware_repair',
53 type=str).split(','))
54
55
Richard Barnette1bf22a32016-11-18 16:14:31 -080056def _is_firmware_repair_supported(host):
57 """
58 Check if a host supports firmware repair.
59
Richard Barnette077665e2016-11-29 16:00:59 -080060 When this function returns true, the DUT should be managed by
61 `FirmwareStatusVerifier` and `FirmwareRepair`, but not
62 `FirmwareVersionVerifier`. In general, this applies to DUTs
63 used for firmware testing.
Richard Barnette1bf22a32016-11-18 16:14:31 -080064
Richard Barnette077665e2016-11-29 16:00:59 -080065 @return A true value if the host should use `FirmwareStatusVerifier`
66 and `FirmwareRepair`; a false value otherwise.
Richard Barnette1bf22a32016-11-18 16:14:31 -080067 """
Prathmesh Prabhub6cea612017-02-09 15:41:19 -080068 info = host.host_info_store.get()
69 return bool(info.pools & _FIRMWARE_REPAIR_POOLS)
Richard Barnette077665e2016-11-29 16:00:59 -080070
71
72def _is_firmware_update_supported(host):
73 """
74 Return whether a DUT should be running the standard firmware.
75
76 In the test lab, DUTs used for general testing, (e.g. the `bvt`
77 pool) need their firmware kept up-to-date with
78 `FirmwareVersionVerifier`. However, some pools have alternative
79 policies for firmware management. This returns whether a given DUT
80 should be updated via the standard stable version update, or
81 managed by some other procedure.
82
83 @param host The host to be checked for update policy.
84 @return A true value if the host should use
85 `FirmwareVersionVerifier`; a false value otherwise.
86 """
Richard Barnettefc465832018-07-13 14:32:16 -070087 return not _is_firmware_repair_supported(host)
Richard Barnette1bf22a32016-11-18 16:14:31 -080088
89
Ningning Xia05af7402018-02-13 18:19:10 -080090def _get_firmware_version(output):
91 """Parse the output and get the firmware version.
92
93 @param output The standard output of chromeos-firmwareupdate script.
94 @return Firmware version if found, else, None.
95 """
96 # At one point, the chromeos-firmwareupdate script was updated to
97 # add "RW" version fields. The old string, "BIOS version:" still
98 # appears in the new output, however it now refers to the RO
99 # firmware version. Therefore, we try searching for the new string
100 # first, "BIOS (RW) version". If that string isn't found, we then
101 # fallback to searching for old string.
102 version = re.search(r'BIOS \(RW\) version:\s*(?P<version>.*)', output)
103
104 if not version:
105 version = re.search(r'BIOS version:\s*(?P<version>.*)', output)
106
107 if version is not None:
108 return version.group('version')
109
110 return None
111
112
113def _get_available_firmware(host, model):
114 """Get the available firmware version given the model.
115
116 @param host The host to get available firmware for.
117 @param model The model name to get corresponding firmware version.
118 @return The available firmware version if found, else, None.
119 """
120 result = host.run('chromeos-firmwareupdate -V', ignore_status=True)
121
122 if result.exit_status == 0:
123 unibuild = False
124 paragraphs = result.stdout.split('\n\n')
125 for p in paragraphs:
126 match = re.search(r'Model:\s*(?P<model>.*)', p)
127 if match:
128 unibuild = True
129 if model == match.group('model'):
130 return _get_firmware_version(p)
131
132 if not unibuild:
133 return _get_firmware_version(result.stdout)
134
135 return None
136
137
Richard Barnette1bf22a32016-11-18 16:14:31 -0800138class FirmwareStatusVerifier(hosts.Verifier):
139 """
140 Verify that a host's firmware is in a good state.
141
142 For DUTs that run firmware tests, it's possible that the firmware
143 on the DUT can get corrupted. This verifier checks whether it
144 appears that firmware should be re-flashed using servo.
145 """
146
147 def verify(self, host):
148 if not _is_firmware_repair_supported(host):
149 return
150 try:
151 # Read the AP firmware and dump the sections that we're
152 # interested in.
153 cmd = ('mkdir /tmp/verify_firmware; '
154 'cd /tmp/verify_firmware; '
155 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
Kevin Shelton0886d3d2018-06-05 13:07:45 -0700156 'do flashrom -r -i $section:$section; '
Richard Barnette1bf22a32016-11-18 16:14:31 -0800157 'done')
158 host.run(cmd)
159
160 # Verify the firmware blocks A and B.
161 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
162 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
163 ' --fv /tmp/verify_firmware/FW_MAIN_%c')
164 for c in ('A', 'B'):
165 rv = host.run(cmd % (c, c), ignore_status=True)
166 if rv.exit_status:
167 raise hosts.AutoservVerifyError(
168 'Firmware %c is in a bad state.' % c)
169 finally:
170 # Remove the temporary files.
171 host.run('rm -rf /tmp/verify_firmware')
172
173 @property
174 def description(self):
175 return 'Firmware on this DUT is clean'
Richard Barnette90ad4262016-11-17 17:29:24 -0800176
177
Richard Barnette077665e2016-11-29 16:00:59 -0800178class FirmwareRepair(hosts.RepairAction):
179 """
180 Reinstall the firmware image using servo.
181
182 This repair function attempts to use servo to install the DUT's
183 designated "stable firmware version".
184
185 This repair method only applies to DUTs used for FAFT.
186 """
187
188 def repair(self, host):
189 if not _is_firmware_repair_supported(host):
190 raise hosts.AutoservRepairError(
191 'Firmware repair is not applicable to host %s.' %
192 host.hostname)
193 if not host.servo:
194 raise hosts.AutoservRepairError(
195 '%s has no servo support.' % host.hostname)
196 host.firmware_install()
197
198 @property
199 def description(self):
200 return 'Re-install the stable firmware via servo'
201
202
Richard Barnette90ad4262016-11-17 17:29:24 -0800203class FirmwareVersionVerifier(hosts.Verifier):
204 """
205 Check for a firmware update, and apply it if appropriate.
206
207 This verifier checks to ensure that either the firmware on the DUT
208 is up-to-date, or that the target firmware can be installed from the
209 currently running build.
210
211 Failure occurs when all of the following apply:
Richard Barnette077665e2016-11-29 16:00:59 -0800212 1. The DUT is not excluded from updates. For example, DUTs used
213 for FAFT testing use `FirmwareRepair` instead.
214 2. The DUT's board has an assigned stable firmware version.
Richard Barnette90ad4262016-11-17 17:29:24 -0800215 3. The DUT is not running the assigned stable firmware.
216 4. The firmware supplied in the running OS build is not the
217 assigned stable firmware.
218
219 If the DUT needs an upgrade and the currently running OS build
Richard Barnette077665e2016-11-29 16:00:59 -0800220 supplies the necessary firmware, the verifier installs the new
221 firmware using `chromeos-firmwareupdate`. Failure to install will
222 cause the verifier to fail.
Richard Barnette90ad4262016-11-17 17:29:24 -0800223
224 This verifier nominally breaks the rule that "verifiers must succeed
225 quickly", since it can invoke `reboot()` during the success code
226 path. We're doing it anyway for two reasons:
227 * The time between updates will typically be measured in months,
228 so the amortized cost is low.
229 * The reason we distinguish repair from verify is to allow
230 rescheduling work immediately while the expensive repair happens
231 out-of-band. But a firmware update will likely hit all DUTs at
232 once, so it's pointless to pass the buck to repair.
233
234 N.B. This verifier is a trigger for all repair actions that install
235 the stable repair image. If the firmware is out-of-date, but the
236 stable repair image does *not* contain the proper firmware version,
237 _the target DUT will fail repair, and will be unable to fix itself_.
238 """
239
240 @staticmethod
241 def _get_rw_firmware(host):
242 result = host.run('crossystem fwid', ignore_status=True)
243 if result.exit_status == 0:
244 return result.stdout
245 else:
246 return None
247
248 @staticmethod
Richard Barnette90ad4262016-11-17 17:29:24 -0800249 def _check_hardware_match(version_a, version_b):
250 """
251 Check that two firmware versions identify the same hardware.
252
253 Firmware version strings look like this:
254 Google_Gnawty.5216.239.34
255 The part before the numbers identifies the hardware for which
256 the firmware was built. This function checks that the hardware
257 identified by `version_a` and `version_b` is the same.
258
259 This is a sanity check to protect us from installing the wrong
260 firmware on a DUT when a board label has somehow gone astray.
261
262 @param version_a First firmware version for the comparison.
263 @param version_b Second firmware version for the comparison.
264 """
265 hardware_a = version_a.split('.')[0]
266 hardware_b = version_b.split('.')[0]
267 if hardware_a != hardware_b:
268 message = 'Hardware/Firmware mismatch updating %s to %s'
269 raise hosts.AutoservVerifyError(
270 message % (version_a, version_b))
271
272 def verify(self, host):
Richard Barnette077665e2016-11-29 16:00:59 -0800273 # Test 1 - The DUT is not excluded from updates.
274 if not _is_firmware_update_supported(host):
Richard Barnette90ad4262016-11-17 17:29:24 -0800275 return
276 # Test 2 - The DUT has an assigned stable firmware version.
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800277 info = host.host_info_store.get()
Ningning Xia05af7402018-02-13 18:19:10 -0800278 if info.model is None:
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800279 raise hosts.AutoservVerifyError(
280 'Can not verify firmware version. '
Ningning Xia05af7402018-02-13 18:19:10 -0800281 'No model label value found')
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800282
Ningning Xia05af7402018-02-13 18:19:10 -0800283 stable_firmware = afe_utils.get_stable_firmware_version(info.model)
Richard Barnette90ad4262016-11-17 17:29:24 -0800284 if stable_firmware is None:
285 # This DUT doesn't have a firmware update target
286 return
287
288 # For tests 3 and 4: If the output from `crossystem` or
289 # `chromeos-firmwareupdate` isn't what we expect, we log an
290 # error, but don't fail: We don't want DUTs unable to test a
291 # build merely because of a bug or change in either of those
292 # commands.
293
294 # Test 3 - The DUT is not running the target stable firmware.
295 current_firmware = self._get_rw_firmware(host)
296 if current_firmware is None:
297 logging.error('DUT firmware version can\'t be determined.')
298 return
299 if current_firmware == stable_firmware:
300 return
301 # Test 4 - The firmware supplied in the running OS build is not
302 # the assigned stable firmware.
Ningning Xia05af7402018-02-13 18:19:10 -0800303 available_firmware = _get_available_firmware(host, info.model)
Richard Barnette90ad4262016-11-17 17:29:24 -0800304 if available_firmware is None:
305 logging.error('Supplied firmware version in OS can\'t be '
306 'determined.')
307 return
308 if available_firmware != stable_firmware:
309 raise hosts.AutoservVerifyError(
310 'DUT firmware requires update from %s to %s' %
311 (current_firmware, stable_firmware))
312 # Time to update the firmware.
313 logging.info('Updating firmware from %s to %s',
314 current_firmware, stable_firmware)
315 self._check_hardware_match(current_firmware, stable_firmware)
316 try:
317 host.run('chromeos-firmwareupdate --mode=autoupdate')
318 host.reboot()
319 except Exception as e:
320 message = ('chromeos-firmwareupdate failed: from '
321 '%s to %s')
322 logging.exception(message, current_firmware, stable_firmware)
323 raise hosts.AutoservVerifyError(
324 message % (current_firmware, stable_firmware))
Richard Barnette1b489932017-02-14 10:50:58 -0800325 final_firmware = self._get_rw_firmware(host)
326 if final_firmware != stable_firmware:
327 message = ('chromeos-firmwareupdate failed: tried upgrade '
328 'to %s, now running %s instead')
329 raise hosts.AutoservVerifyError(
330 message % (stable_firmware, final_firmware))
Richard Barnette90ad4262016-11-17 17:29:24 -0800331
332 @property
333 def description(self):
334 return 'The firmware on this DUT is up-to-date'