blob: 255f57571edef668d5fa49d4d916491a8d3cdc00 [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.
11"""
12
Richard Barnette90ad4262016-11-17 17:29:24 -080013import logging
14import re
15
16import common
Richard Barnette1bf22a32016-11-18 16:14:31 -080017from autotest_lib.client.common_lib import global_config
Richard Barnette90ad4262016-11-17 17:29:24 -080018from autotest_lib.client.common_lib import hosts
19from autotest_lib.server import afe_utils
Richard Barnette1bf22a32016-11-18 16:14:31 -080020from autotest_lib.site_utils.suite_scheduler import constants
21
22
23_CONFIG = global_config.global_config
24_FIRMWARE_REPAIR_POOLS = set(_CONFIG.get_config_value('CROS',
25 'pools_support_firmware_repair', type=str).split(','))
26_POOL_PREFIX = constants.Labels.POOL_PREFIX
27
28def _is_firmware_repair_supported(host):
29 """
30 Check if a host supports firmware repair.
31
32 Firmware repair is only applicable to DUTs in pools listed in
33 the global config setting CROS/pools_support_firmware_repair.
34
35 @return: `True` if it is supported, or `False` otherwise.
36 """
37 pool_labels = afe_utils.get_labels(host, _POOL_PREFIX)
38 pools = set([l[len(_POOL_PREFIX) : ] for l in pool_labels])
39 return bool(pools & _FIRMWARE_REPAIR_POOLS)
40
41
42class FirmwareStatusVerifier(hosts.Verifier):
43 """
44 Verify that a host's firmware is in a good state.
45
46 For DUTs that run firmware tests, it's possible that the firmware
47 on the DUT can get corrupted. This verifier checks whether it
48 appears that firmware should be re-flashed using servo.
49 """
50
51 def verify(self, host):
52 if not _is_firmware_repair_supported(host):
53 return
54 try:
55 # Read the AP firmware and dump the sections that we're
56 # interested in.
57 cmd = ('mkdir /tmp/verify_firmware; '
58 'cd /tmp/verify_firmware; '
59 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
60 'do flashrom -r image.bin -i $section:$section; '
61 'done')
62 host.run(cmd)
63
64 # Verify the firmware blocks A and B.
65 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
66 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
67 ' --fv /tmp/verify_firmware/FW_MAIN_%c')
68 for c in ('A', 'B'):
69 rv = host.run(cmd % (c, c), ignore_status=True)
70 if rv.exit_status:
71 raise hosts.AutoservVerifyError(
72 'Firmware %c is in a bad state.' % c)
73 finally:
74 # Remove the temporary files.
75 host.run('rm -rf /tmp/verify_firmware')
76
77 @property
78 def description(self):
79 return 'Firmware on this DUT is clean'
Richard Barnette90ad4262016-11-17 17:29:24 -080080
81
82class FirmwareVersionVerifier(hosts.Verifier):
83 """
84 Check for a firmware update, and apply it if appropriate.
85
86 This verifier checks to ensure that either the firmware on the DUT
87 is up-to-date, or that the target firmware can be installed from the
88 currently running build.
89
90 Failure occurs when all of the following apply:
91 1. The DUT is not part of a FAFT pool. (DUTs used for FAFT testing
92 instead use `FirmwareRepair`, below.)
93 2. The DUT has an assigned stable firmware version.
94 3. The DUT is not running the assigned stable firmware.
95 4. The firmware supplied in the running OS build is not the
96 assigned stable firmware.
97
98 If the DUT needs an upgrade and the currently running OS build
99 supplies the necessary firmware, use `chromeos-firmwareupdate` to
100 install the new firmware. Failure to install will cause the
101 verifier to fail.
102
103 This verifier nominally breaks the rule that "verifiers must succeed
104 quickly", since it can invoke `reboot()` during the success code
105 path. We're doing it anyway for two reasons:
106 * The time between updates will typically be measured in months,
107 so the amortized cost is low.
108 * The reason we distinguish repair from verify is to allow
109 rescheduling work immediately while the expensive repair happens
110 out-of-band. But a firmware update will likely hit all DUTs at
111 once, so it's pointless to pass the buck to repair.
112
113 N.B. This verifier is a trigger for all repair actions that install
114 the stable repair image. If the firmware is out-of-date, but the
115 stable repair image does *not* contain the proper firmware version,
116 _the target DUT will fail repair, and will be unable to fix itself_.
117 """
118
119 @staticmethod
120 def _get_rw_firmware(host):
121 result = host.run('crossystem fwid', ignore_status=True)
122 if result.exit_status == 0:
123 return result.stdout
124 else:
125 return None
126
127 @staticmethod
128 def _get_available_firmware(host):
129 result = host.run('chromeos-firmwareupdate -V',
130 ignore_status=True)
131 if result.exit_status == 0:
132 version = re.search(r'BIOS version:\s*(?P<version>.*)',
133 result.stdout)
134 if version is not None:
135 return version.group('version')
136 return None
137
138 @staticmethod
139 def _check_hardware_match(version_a, version_b):
140 """
141 Check that two firmware versions identify the same hardware.
142
143 Firmware version strings look like this:
144 Google_Gnawty.5216.239.34
145 The part before the numbers identifies the hardware for which
146 the firmware was built. This function checks that the hardware
147 identified by `version_a` and `version_b` is the same.
148
149 This is a sanity check to protect us from installing the wrong
150 firmware on a DUT when a board label has somehow gone astray.
151
152 @param version_a First firmware version for the comparison.
153 @param version_b Second firmware version for the comparison.
154 """
155 hardware_a = version_a.split('.')[0]
156 hardware_b = version_b.split('.')[0]
157 if hardware_a != hardware_b:
158 message = 'Hardware/Firmware mismatch updating %s to %s'
159 raise hosts.AutoservVerifyError(
160 message % (version_a, version_b))
161
162 def verify(self, host):
163 # Test 1 - The DUT is not part of a FAFT pool.
Richard Barnette1bf22a32016-11-18 16:14:31 -0800164 if _is_firmware_repair_supported(host):
Richard Barnette90ad4262016-11-17 17:29:24 -0800165 return
166 # Test 2 - The DUT has an assigned stable firmware version.
167 stable_firmware = afe_utils.get_stable_firmware_version(
168 host._get_board_from_afe())
169 if stable_firmware is None:
170 # This DUT doesn't have a firmware update target
171 return
172
173 # For tests 3 and 4: If the output from `crossystem` or
174 # `chromeos-firmwareupdate` isn't what we expect, we log an
175 # error, but don't fail: We don't want DUTs unable to test a
176 # build merely because of a bug or change in either of those
177 # commands.
178
179 # Test 3 - The DUT is not running the target stable firmware.
180 current_firmware = self._get_rw_firmware(host)
181 if current_firmware is None:
182 logging.error('DUT firmware version can\'t be determined.')
183 return
184 if current_firmware == stable_firmware:
185 return
186 # Test 4 - The firmware supplied in the running OS build is not
187 # the assigned stable firmware.
188 available_firmware = self._get_available_firmware(host)
189 if available_firmware is None:
190 logging.error('Supplied firmware version in OS can\'t be '
191 'determined.')
192 return
193 if available_firmware != stable_firmware:
194 raise hosts.AutoservVerifyError(
195 'DUT firmware requires update from %s to %s' %
196 (current_firmware, stable_firmware))
197 # Time to update the firmware.
198 logging.info('Updating firmware from %s to %s',
199 current_firmware, stable_firmware)
200 self._check_hardware_match(current_firmware, stable_firmware)
201 try:
202 host.run('chromeos-firmwareupdate --mode=autoupdate')
203 host.reboot()
204 except Exception as e:
205 message = ('chromeos-firmwareupdate failed: from '
206 '%s to %s')
207 logging.exception(message, current_firmware, stable_firmware)
208 raise hosts.AutoservVerifyError(
209 message % (current_firmware, stable_firmware))
210
211 @property
212 def description(self):
213 return 'The firmware on this DUT is up-to-date'
214
215
216class FirmwareRepair(hosts.RepairAction):
217 """
218 Reinstall the firmware image using servo.
219
220 This repair function attempts to use servo to install the DUT's
221 designated "stable firmware version".
222
223 This repair method only applies to DUTs used for FAFT.
224 """
225
226 def repair(self, host):
Richard Barnette1bf22a32016-11-18 16:14:31 -0800227 if not _is_firmware_repair_supported(host):
Richard Barnette90ad4262016-11-17 17:29:24 -0800228 raise hosts.AutoservRepairError(
229 'Firmware repair is not applicable to host %s.' %
230 host.hostname)
231 if not host.servo:
232 raise hosts.AutoservRepairError(
233 '%s has no servo support.' % host.hostname)
234 host.firmware_install()
235
236 @property
237 def description(self):
238 return 'Re-install the stable firmware via servo'