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