• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 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
5"""
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
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.
33"""
34
35import logging
36import re
37
38import common
39from autotest_lib.client.common_lib import global_config
40from autotest_lib.client.common_lib import hosts
41from autotest_lib.server import afe_utils
42from autotest_lib.site_utils.suite_scheduler import constants
43
44
45# _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
61def _is_firmware_repair_supported(host):
62    """
63    Check if a host supports firmware repair.
64
65    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.
69
70    @return A true value if the host should use `FirmwareStatusVerifier`
71            and `FirmwareRepair`; a false value otherwise.
72    """
73    info = host.host_info_store.get()
74    return bool(info.pools & _FIRMWARE_REPAIR_POOLS)
75
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    """
92    info = host.host_info_store.get()
93    return bool(info.pools & _FIRMWARE_UPDATE_POOLS)
94
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'
134
135
136class 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
161class 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:
170     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.
173     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
178    supplies the necessary firmware, the verifier installs the new
179    firmware using `chromeos-firmwareupdate`.  Failure to install will
180    cause the verifier to fail.
181
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:
211            version = re.search(r'BIOS version:\s*(?P<version>.*)',
212                                result.stdout)
213            if version is not None:
214                return version.group('version')
215        return None
216
217    @staticmethod
218    def _check_hardware_match(version_a, version_b):
219        """
220        Check that two firmware versions identify the same hardware.
221
222        Firmware version strings look like this:
223            Google_Gnawty.5216.239.34
224        The part before the numbers identifies the hardware for which
225        the firmware was built.  This function checks that the hardware
226        identified by `version_a` and `version_b` is the same.
227
228        This is a sanity check to protect us from installing the wrong
229        firmware on a DUT when a board label has somehow gone astray.
230
231        @param version_a  First firmware version for the comparison.
232        @param version_b  Second firmware version for the comparison.
233        """
234        hardware_a = version_a.split('.')[0]
235        hardware_b = version_b.split('.')[0]
236        if hardware_a != hardware_b:
237            message = 'Hardware/Firmware mismatch updating %s to %s'
238            raise hosts.AutoservVerifyError(
239                    message % (version_a, version_b))
240
241    def verify(self, host):
242        # Test 1 - The DUT is not excluded from updates.
243        if not _is_firmware_update_supported(host):
244            return
245        # Test 2 - The DUT has an assigned stable firmware version.
246        info = host.host_info_store.get()
247        if info.board is None:
248            raise hosts.AutoservVerifyError(
249                    'Can not verify firmware version. '
250                    'No board label value found')
251
252        stable_firmware = afe_utils.get_stable_firmware_version(info.board)
253        if stable_firmware is None:
254            # This DUT doesn't have a firmware update target
255            return
256
257        # For tests 3 and 4:  If the output from `crossystem` or
258        # `chromeos-firmwareupdate` isn't what we expect, we log an
259        # error, but don't fail:  We don't want DUTs unable to test a
260        # build merely because of a bug or change in either of those
261        # commands.
262
263        # Test 3 - The DUT is not running the target stable firmware.
264        current_firmware = self._get_rw_firmware(host)
265        if current_firmware is None:
266            logging.error('DUT firmware version can\'t be determined.')
267            return
268        if current_firmware == stable_firmware:
269            return
270        # Test 4 - The firmware supplied in the running OS build is not
271        # the assigned stable firmware.
272        available_firmware = self._get_available_firmware(host)
273        if available_firmware is None:
274            logging.error('Supplied firmware version in OS can\'t be '
275                          'determined.')
276            return
277        if available_firmware != stable_firmware:
278            raise hosts.AutoservVerifyError(
279                    'DUT firmware requires update from %s to %s' %
280                    (current_firmware, stable_firmware))
281        # Time to update the firmware.
282        logging.info('Updating firmware from %s to %s',
283                     current_firmware, stable_firmware)
284        self._check_hardware_match(current_firmware, stable_firmware)
285        try:
286            host.run('chromeos-firmwareupdate --mode=autoupdate')
287            host.reboot()
288        except Exception as e:
289            message = ('chromeos-firmwareupdate failed: from '
290                       '%s to %s')
291            logging.exception(message, current_firmware, stable_firmware)
292            raise hosts.AutoservVerifyError(
293                    message % (current_firmware, stable_firmware))
294        final_firmware = self._get_rw_firmware(host)
295        if final_firmware != stable_firmware:
296            message = ('chromeos-firmwareupdate failed: tried upgrade '
297                       'to %s, now running %s instead')
298            raise hosts.AutoservVerifyError(
299                    message % (stable_firmware, final_firmware))
300
301    @property
302    def description(self):
303        return 'The firmware on this DUT is up-to-date'
304