blob: 13d50365c9f983e750242270670c8d4447a135ae [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
Hung-Te Lina014dbc2019-11-07 16:41:42 +080037import json
Richard Barnette90ad4262016-11-17 17:29:24 -080038import logging
39import re
40
41import common
Richard Barnette1bf22a32016-11-18 16:14:31 -080042from autotest_lib.client.common_lib import global_config
Richard Barnette90ad4262016-11-17 17:29:24 -080043from autotest_lib.client.common_lib import hosts
44from autotest_lib.server import afe_utils
Richard Barnette3245ae22018-08-31 11:50:08 -070045from autotest_lib.server.hosts import repair_utils
Richard Barnette1bf22a32016-11-18 16:14:31 -080046
47
Richard Barnette077665e2016-11-29 16:00:59 -080048# _FIRMWARE_REPAIR_POOLS - The set of pools that should be
49# managed by `FirmwareStatusVerifier` and `FirmwareRepair`.
50#
51_FIRMWARE_REPAIR_POOLS = set(
52 global_config.global_config.get_config_value(
53 'CROS',
54 'pools_support_firmware_repair',
55 type=str).split(','))
56
57
Richard Barnette1bf22a32016-11-18 16:14:31 -080058def _is_firmware_repair_supported(host):
59 """
60 Check if a host supports firmware repair.
61
Richard Barnette077665e2016-11-29 16:00:59 -080062 When this function returns true, the DUT should be managed by
63 `FirmwareStatusVerifier` and `FirmwareRepair`, but not
64 `FirmwareVersionVerifier`. In general, this applies to DUTs
65 used for firmware testing.
Richard Barnette1bf22a32016-11-18 16:14:31 -080066
Richard Barnette077665e2016-11-29 16:00:59 -080067 @return A true value if the host should use `FirmwareStatusVerifier`
68 and `FirmwareRepair`; a false value otherwise.
Richard Barnette1bf22a32016-11-18 16:14:31 -080069 """
Prathmesh Prabhub6cea612017-02-09 15:41:19 -080070 info = host.host_info_store.get()
71 return bool(info.pools & _FIRMWARE_REPAIR_POOLS)
Richard Barnette077665e2016-11-29 16:00:59 -080072
73
74def _is_firmware_update_supported(host):
75 """
76 Return whether a DUT should be running the standard firmware.
77
78 In the test lab, DUTs used for general testing, (e.g. the `bvt`
79 pool) need their firmware kept up-to-date with
80 `FirmwareVersionVerifier`. However, some pools have alternative
81 policies for firmware management. This returns whether a given DUT
82 should be updated via the standard stable version update, or
83 managed by some other procedure.
84
85 @param host The host to be checked for update policy.
86 @return A true value if the host should use
87 `FirmwareVersionVerifier`; a false value otherwise.
88 """
Richard Barnettefc465832018-07-13 14:32:16 -070089 return not _is_firmware_repair_supported(host)
Richard Barnette1bf22a32016-11-18 16:14:31 -080090
91
Ningning Xia05af7402018-02-13 18:19:10 -080092def _get_available_firmware(host, model):
Hung-Te Lina014dbc2019-11-07 16:41:42 +080093 """Get the available RW firmware version given the model.
Ningning Xia05af7402018-02-13 18:19:10 -080094
95 @param host The host to get available firmware for.
96 @param model The model name to get corresponding firmware version.
Hung-Te Lina014dbc2019-11-07 16:41:42 +080097 @return The available RW firmware version if found, else, None.
Ningning Xia05af7402018-02-13 18:19:10 -080098 """
Hung-Te Lina014dbc2019-11-07 16:41:42 +080099 result = host.run('chromeos-firmwareupdate --manifest', ignore_status=True)
Ningning Xia05af7402018-02-13 18:19:10 -0800100
Hung-Te Lina014dbc2019-11-07 16:41:42 +0800101 if result.exit_status != 0:
102 return None
Ningning Xia05af7402018-02-13 18:19:10 -0800103
Hung-Te Lina014dbc2019-11-07 16:41:42 +0800104 # The manifest is a JSON in .model.host.versions.rw
105 data = json.loads(result.stdout) or {}
106 key = model if len(data) > 1 else next(data.iterkeys(), '')
107 key += '.host.versions.rw'
108 for k in key.split('.'):
109 data = data.get(k, {})
110 return data or None
Ningning Xia05af7402018-02-13 18:19:10 -0800111
112
Richard Barnette1bf22a32016-11-18 16:14:31 -0800113class FirmwareStatusVerifier(hosts.Verifier):
114 """
115 Verify that a host's firmware is in a good state.
116
117 For DUTs that run firmware tests, it's possible that the firmware
118 on the DUT can get corrupted. This verifier checks whether it
119 appears that firmware should be re-flashed using servo.
120 """
121
122 def verify(self, host):
123 if not _is_firmware_repair_supported(host):
124 return
125 try:
126 # Read the AP firmware and dump the sections that we're
127 # interested in.
128 cmd = ('mkdir /tmp/verify_firmware; '
129 'cd /tmp/verify_firmware; '
130 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
Chris McDonald9e6f9df2018-10-03 12:12:06 -0600131 'do flashrom -p host -r -i $section:$section; '
Richard Barnette1bf22a32016-11-18 16:14:31 -0800132 'done')
133 host.run(cmd)
134
135 # Verify the firmware blocks A and B.
136 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
137 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
138 ' --fv /tmp/verify_firmware/FW_MAIN_%c')
139 for c in ('A', 'B'):
140 rv = host.run(cmd % (c, c), ignore_status=True)
141 if rv.exit_status:
142 raise hosts.AutoservVerifyError(
143 'Firmware %c is in a bad state.' % c)
144 finally:
145 # Remove the temporary files.
146 host.run('rm -rf /tmp/verify_firmware')
147
148 @property
149 def description(self):
150 return 'Firmware on this DUT is clean'
Richard Barnette90ad4262016-11-17 17:29:24 -0800151
152
Richard Barnette077665e2016-11-29 16:00:59 -0800153class FirmwareRepair(hosts.RepairAction):
154 """
155 Reinstall the firmware image using servo.
156
157 This repair function attempts to use servo to install the DUT's
158 designated "stable firmware version".
159
160 This repair method only applies to DUTs used for FAFT.
161 """
162
163 def repair(self, host):
164 if not _is_firmware_repair_supported(host):
165 raise hosts.AutoservRepairError(
166 'Firmware repair is not applicable to host %s.' %
Garry Wang954f8382019-01-23 13:49:29 -0800167 host.hostname, 'not_applicable')
Richard Barnette3245ae22018-08-31 11:50:08 -0700168 repair_utils.require_servo(host)
Richard Barnette077665e2016-11-29 16:00:59 -0800169 host.firmware_install()
170
171 @property
172 def description(self):
173 return 'Re-install the stable firmware via servo'
174
175
Richard Barnette90ad4262016-11-17 17:29:24 -0800176class FirmwareVersionVerifier(hosts.Verifier):
177 """
178 Check for a firmware update, and apply it if appropriate.
179
180 This verifier checks to ensure that either the firmware on the DUT
181 is up-to-date, or that the target firmware can be installed from the
182 currently running build.
183
184 Failure occurs when all of the following apply:
Richard Barnette077665e2016-11-29 16:00:59 -0800185 1. The DUT is not excluded from updates. For example, DUTs used
186 for FAFT testing use `FirmwareRepair` instead.
187 2. The DUT's board has an assigned stable firmware version.
Richard Barnette90ad4262016-11-17 17:29:24 -0800188 3. The DUT is not running the assigned stable firmware.
189 4. The firmware supplied in the running OS build is not the
190 assigned stable firmware.
191
192 If the DUT needs an upgrade and the currently running OS build
Richard Barnette077665e2016-11-29 16:00:59 -0800193 supplies the necessary firmware, the verifier installs the new
194 firmware using `chromeos-firmwareupdate`. Failure to install will
195 cause the verifier to fail.
Richard Barnette90ad4262016-11-17 17:29:24 -0800196
197 This verifier nominally breaks the rule that "verifiers must succeed
198 quickly", since it can invoke `reboot()` during the success code
199 path. We're doing it anyway for two reasons:
200 * The time between updates will typically be measured in months,
201 so the amortized cost is low.
202 * The reason we distinguish repair from verify is to allow
203 rescheduling work immediately while the expensive repair happens
204 out-of-band. But a firmware update will likely hit all DUTs at
205 once, so it's pointless to pass the buck to repair.
206
207 N.B. This verifier is a trigger for all repair actions that install
208 the stable repair image. If the firmware is out-of-date, but the
209 stable repair image does *not* contain the proper firmware version,
210 _the target DUT will fail repair, and will be unable to fix itself_.
211 """
212
213 @staticmethod
214 def _get_rw_firmware(host):
215 result = host.run('crossystem fwid', ignore_status=True)
216 if result.exit_status == 0:
217 return result.stdout
218 else:
219 return None
220
221 @staticmethod
Richard Barnette90ad4262016-11-17 17:29:24 -0800222 def _check_hardware_match(version_a, version_b):
223 """
224 Check that two firmware versions identify the same hardware.
225
226 Firmware version strings look like this:
227 Google_Gnawty.5216.239.34
228 The part before the numbers identifies the hardware for which
229 the firmware was built. This function checks that the hardware
230 identified by `version_a` and `version_b` is the same.
231
232 This is a sanity check to protect us from installing the wrong
233 firmware on a DUT when a board label has somehow gone astray.
234
235 @param version_a First firmware version for the comparison.
236 @param version_b Second firmware version for the comparison.
237 """
238 hardware_a = version_a.split('.')[0]
239 hardware_b = version_b.split('.')[0]
240 if hardware_a != hardware_b:
241 message = 'Hardware/Firmware mismatch updating %s to %s'
242 raise hosts.AutoservVerifyError(
243 message % (version_a, version_b))
244
245 def verify(self, host):
Richard Barnette077665e2016-11-29 16:00:59 -0800246 # Test 1 - The DUT is not excluded from updates.
247 if not _is_firmware_update_supported(host):
Richard Barnette90ad4262016-11-17 17:29:24 -0800248 return
249 # Test 2 - The DUT has an assigned stable firmware version.
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800250 info = host.host_info_store.get()
Ningning Xia05af7402018-02-13 18:19:10 -0800251 if info.model is None:
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800252 raise hosts.AutoservVerifyError(
253 'Can not verify firmware version. '
Ningning Xia05af7402018-02-13 18:19:10 -0800254 'No model label value found')
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800255
C Shapiro70b70672019-05-24 11:26:16 -0600256 stable_firmware = None
257 try:
Gregory Nisbet7fe11c22019-11-22 11:06:06 -0800258 stable_firmware = afe_utils.get_stable_firmware_version_v2(info)
C Shapiro70b70672019-05-24 11:26:16 -0600259 except Exception as e:
260 logging.exception('Failed lookup to AFE for stable fw version '
261 ' with exception: %s', e)
262
Richard Barnette90ad4262016-11-17 17:29:24 -0800263 if stable_firmware is None:
264 # This DUT doesn't have a firmware update target
265 return
266
267 # For tests 3 and 4: If the output from `crossystem` or
268 # `chromeos-firmwareupdate` isn't what we expect, we log an
269 # error, but don't fail: We don't want DUTs unable to test a
270 # build merely because of a bug or change in either of those
271 # commands.
272
273 # Test 3 - The DUT is not running the target stable firmware.
274 current_firmware = self._get_rw_firmware(host)
275 if current_firmware is None:
276 logging.error('DUT firmware version can\'t be determined.')
277 return
278 if current_firmware == stable_firmware:
279 return
280 # Test 4 - The firmware supplied in the running OS build is not
281 # the assigned stable firmware.
Ningning Xia05af7402018-02-13 18:19:10 -0800282 available_firmware = _get_available_firmware(host, info.model)
Richard Barnette90ad4262016-11-17 17:29:24 -0800283 if available_firmware is None:
284 logging.error('Supplied firmware version in OS can\'t be '
285 'determined.')
286 return
287 if available_firmware != stable_firmware:
288 raise hosts.AutoservVerifyError(
289 'DUT firmware requires update from %s to %s' %
290 (current_firmware, stable_firmware))
291 # Time to update the firmware.
292 logging.info('Updating firmware from %s to %s',
293 current_firmware, stable_firmware)
294 self._check_hardware_match(current_firmware, stable_firmware)
295 try:
296 host.run('chromeos-firmwareupdate --mode=autoupdate')
297 host.reboot()
298 except Exception as e:
299 message = ('chromeos-firmwareupdate failed: from '
300 '%s to %s')
301 logging.exception(message, current_firmware, stable_firmware)
302 raise hosts.AutoservVerifyError(
303 message % (current_firmware, stable_firmware))
Richard Barnette1b489932017-02-14 10:50:58 -0800304 final_firmware = self._get_rw_firmware(host)
305 if final_firmware != stable_firmware:
306 message = ('chromeos-firmwareupdate failed: tried upgrade '
307 'to %s, now running %s instead')
308 raise hosts.AutoservVerifyError(
309 message % (stable_firmware, final_firmware))
Richard Barnette90ad4262016-11-17 17:29:24 -0800310
311 @property
312 def description(self):
313 return 'The firmware on this DUT is up-to-date'