xref: /aosp_15_r20/external/autotest/server/hosts/cros_firmware.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2016 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""
7*9c5db199SXin LiRepair actions and verifiers relating to CrOS firmware.
8*9c5db199SXin Li
9*9c5db199SXin LiThis contains the repair actions and verifiers need to find problems
10*9c5db199SXin Liwith the firmware installed on ChromeOS DUTs, and when necessary, to
11*9c5db199SXin Lifix problems by updating or re-installing the firmware.
12*9c5db199SXin Li
13*9c5db199SXin LiThe operations in the module support two distinct use cases:
14*9c5db199SXin Li  * DUTs used for FAFT tests can in some cases have problems with
15*9c5db199SXin Li    corrupted firmware.  The module supplies `FirmwareStatusVerifier`
16*9c5db199SXin Li    to check for corruption, and supplies `FaftFirmwareRepair` to
17*9c5db199SXin Li    re-install firmware of current faft stable_version via servo
18*9c5db199SXin Li    when needed.
19*9c5db199SXin Li  * DUTs used for general testing normally should be running a
20*9c5db199SXin Li    designated "stable" firmware version.  This module supplies
21*9c5db199SXin Li    `FirmwareVersionVerifier` to detect and automatically update
22*9c5db199SXin Li    firmware that is out-of-date from the designated version. This model
23*9c5db199SXin Li    also supplys `GeneralFirmwareRepair` to re-install firmware that
24*9c5db199SXin Li    tied with current stable_version image via servo when needed.
25*9c5db199SXin Li
26*9c5db199SXin LiFor purposes of the operations in the module, we distinguish three kinds
27*9c5db199SXin Liof DUT, based on pool assignments:
28*9c5db199SXin Li  * DUTs used for general testing.  These DUTs automatically check for
29*9c5db199SXin Li    and install the stable firmware using `FirmwareVersionVerifier`.
30*9c5db199SXin Li  * DUTs in pools used for FAFT testing.  These check for bad firmware
31*9c5db199SXin Li    builds with `FirmwareStatusVerifier`, and will fix problems using
32*9c5db199SXin Li    `FirmwareRepair`.  These DUTs don't check for or install the
33*9c5db199SXin Li    stable firmware.
34*9c5db199SXin Li  * DUTs not in general pools, and not used for FAFT.  These DUTs
35*9c5db199SXin Li    are expected to be managed by separate processes and are excluded
36*9c5db199SXin Li    from all of the verification and repair code in this module.
37*9c5db199SXin Li"""
38*9c5db199SXin Li
39*9c5db199SXin Li# pylint: disable=missing-docstring
40*9c5db199SXin Li
41*9c5db199SXin Lifrom __future__ import absolute_import
42*9c5db199SXin Lifrom __future__ import division
43*9c5db199SXin Lifrom __future__ import print_function
44*9c5db199SXin Li
45*9c5db199SXin Liimport json
46*9c5db199SXin Liimport logging
47*9c5db199SXin Li
48*9c5db199SXin Liimport common
49*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
50*9c5db199SXin Lifrom autotest_lib.client.common_lib import hosts
51*9c5db199SXin Lifrom autotest_lib.server import afe_utils
52*9c5db199SXin Lifrom autotest_lib.server.hosts import repair_utils
53*9c5db199SXin Lifrom autotest_lib.server.hosts import cros_constants
54*9c5db199SXin Li
55*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import timeout_util
56*9c5db199SXin Liimport six
57*9c5db199SXin Li
58*9c5db199SXin Li
59*9c5db199SXin Li# _FIRMWARE_REPAIR_POOLS - The set of pools that should be
60*9c5db199SXin Li# managed by `FirmwareStatusVerifier` and `FirmwareRepair`.
61*9c5db199SXin Li#
62*9c5db199SXin Li_FIRMWARE_REPAIR_POOLS = set(
63*9c5db199SXin Li    global_config.global_config.get_config_value(
64*9c5db199SXin Li            'CROS',
65*9c5db199SXin Li            'pools_support_firmware_repair',
66*9c5db199SXin Li            type=str).split(','))
67*9c5db199SXin Li
68*9c5db199SXin Li
69*9c5db199SXin Lidef _is_firmware_testing_device(host):
70*9c5db199SXin Li    """
71*9c5db199SXin Li    check if a host is dedicated for firmware testing.
72*9c5db199SXin Li
73*9c5db199SXin Li    When this function returns true, the DUT should be managed by
74*9c5db199SXin Li    `FirmwareStatusVerifier` and `FaftFirmwareRepair`, but not
75*9c5db199SXin Li    `FirmwareVersionVerifier` and `GeneralFirmwareRepair.
76*9c5db199SXin Li
77*9c5db199SXin Li    @return A true value if the host should use `FirmwareStatusVerifier`
78*9c5db199SXin Li            and `FaftFirmwareRepair`; a false value otherwise.
79*9c5db199SXin Li    """
80*9c5db199SXin Li    info = host.host_info_store.get()
81*9c5db199SXin Li    return bool(info.pools & _FIRMWARE_REPAIR_POOLS)
82*9c5db199SXin Li
83*9c5db199SXin Li
84*9c5db199SXin Lidef _is_firmware_update_supported(host):
85*9c5db199SXin Li    """
86*9c5db199SXin Li    Return whether a DUT should be running the standard firmware.
87*9c5db199SXin Li
88*9c5db199SXin Li    In the test lab, DUTs used for general testing, (e.g. the `bvt`
89*9c5db199SXin Li    pool) need their firmware kept up-to-date with
90*9c5db199SXin Li    `FirmwareVersionVerifier`.  However, some pools have alternative
91*9c5db199SXin Li    policies for firmware management.  This returns whether a given DUT
92*9c5db199SXin Li    should be updated via the standard stable version update, or
93*9c5db199SXin Li    managed by some other procedure.
94*9c5db199SXin Li
95*9c5db199SXin Li    @param host   The host to be checked for update policy.
96*9c5db199SXin Li    @return A true value if the host should use
97*9c5db199SXin Li            `FirmwareVersionVerifier`; a false value otherwise.
98*9c5db199SXin Li    """
99*9c5db199SXin Li    return not _is_firmware_testing_device(host)
100*9c5db199SXin Li
101*9c5db199SXin Li
102*9c5db199SXin Lidef _get_available_firmware(host, model):
103*9c5db199SXin Li    """Get the available RW firmware version given the model.
104*9c5db199SXin Li
105*9c5db199SXin Li    @param host     The host to get available firmware for.
106*9c5db199SXin Li    @param model    The model name to get corresponding firmware version.
107*9c5db199SXin Li    @return The available RW firmware version if found, else, None.
108*9c5db199SXin Li    """
109*9c5db199SXin Li    result = host.run('chromeos-firmwareupdate --manifest', ignore_status=True)
110*9c5db199SXin Li
111*9c5db199SXin Li    if result.exit_status != 0:
112*9c5db199SXin Li        return None
113*9c5db199SXin Li
114*9c5db199SXin Li    # The manifest is a JSON in .model.host.versions.rw
115*9c5db199SXin Li    data = json.loads(result.stdout) or {}
116*9c5db199SXin Li    key = model if len(data) > 1 else next(six.iterkeys(data), '')
117*9c5db199SXin Li    key += '.host.versions.rw'
118*9c5db199SXin Li    for k in key.split('.'):
119*9c5db199SXin Li        data = data.get(k, {})
120*9c5db199SXin Li    return data or None
121*9c5db199SXin Li
122*9c5db199SXin Li
123*9c5db199SXin Liclass FirmwareStatusVerifier(hosts.Verifier):
124*9c5db199SXin Li    """
125*9c5db199SXin Li    Verify that a host's firmware is in a good state.
126*9c5db199SXin Li
127*9c5db199SXin Li    For DUTs that run firmware tests, it's possible that the firmware
128*9c5db199SXin Li    on the DUT can get corrupted.  This verifier checks whether it
129*9c5db199SXin Li    appears that firmware should be re-flashed using servo.
130*9c5db199SXin Li    """
131*9c5db199SXin Li
132*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
133*9c5db199SXin Li    def verify(self, host):
134*9c5db199SXin Li        if not _is_firmware_testing_device(host):
135*9c5db199SXin Li            return
136*9c5db199SXin Li        try:
137*9c5db199SXin Li            # Read the AP firmware and dump the sections that we're
138*9c5db199SXin Li            # interested in.
139*9c5db199SXin Li            cmd = ('mkdir /tmp/verify_firmware; '
140*9c5db199SXin Li                   'cd /tmp/verify_firmware; '
141*9c5db199SXin Li                   'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
142*9c5db199SXin Li                   'do flashrom -p host -r -i $section:$section; '
143*9c5db199SXin Li                   'done')
144*9c5db199SXin Li            host.run(cmd)
145*9c5db199SXin Li
146*9c5db199SXin Li            # Verify the firmware blocks A and B.
147*9c5db199SXin Li            cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
148*9c5db199SXin Li                   ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
149*9c5db199SXin Li                   ' --fv /tmp/verify_firmware/FW_MAIN_%c')
150*9c5db199SXin Li            for c in ('A', 'B'):
151*9c5db199SXin Li                rv = host.run(cmd % (c, c), ignore_status=True)
152*9c5db199SXin Li                if rv.exit_status:
153*9c5db199SXin Li                    raise hosts.AutoservVerifyError(
154*9c5db199SXin Li                            'Firmware %c is in a bad state.' % c)
155*9c5db199SXin Li        finally:
156*9c5db199SXin Li            # Remove the temporary files.
157*9c5db199SXin Li            host.run('rm -rf /tmp/verify_firmware')
158*9c5db199SXin Li
159*9c5db199SXin Li    @property
160*9c5db199SXin Li    def description(self):
161*9c5db199SXin Li        return 'Firmware on this DUT is clean'
162*9c5db199SXin Li
163*9c5db199SXin Li
164*9c5db199SXin Liclass FirmwareRepair(hosts.RepairAction):
165*9c5db199SXin Li    """
166*9c5db199SXin Li    Reinstall the firmware image using servo.
167*9c5db199SXin Li
168*9c5db199SXin Li    This repair function attempts to use servo to install the DUT's
169*9c5db199SXin Li    designated "stable firmware version".
170*9c5db199SXin Li
171*9c5db199SXin Li    This repair method only applies to DUTs used for FAFT.
172*9c5db199SXin Li    """
173*9c5db199SXin Li
174*9c5db199SXin Li    def _get_faft_stable_build(self, host):
175*9c5db199SXin Li        info = host.host_info_store.get()
176*9c5db199SXin Li        return afe_utils.get_stable_faft_version_v2(info)
177*9c5db199SXin Li
178*9c5db199SXin Li    def _get_os_stable_build(self, host):
179*9c5db199SXin Li        # Use firmware in current stable os build.
180*9c5db199SXin Li        return host.get_cros_repair_image_name()
181*9c5db199SXin Li
182*9c5db199SXin Li    def _run_faft_repair(self, host, build):
183*9c5db199SXin Li        host.firmware_install(build)
184*9c5db199SXin Li
185*9c5db199SXin Li    def _run_general_repair(self, host, build):
186*9c5db199SXin Li        # As GeneralFirmwareRepair is the last repair action, we expect
187*9c5db199SXin Li        # stable_version os image is loaded on usbkey during other repair
188*9c5db199SXin Li        # action runs. And there is also no point to repeat and waste time if
189*9c5db199SXin Li        # download image to usbkey failed in other repair actions.
190*9c5db199SXin Li        if host._servo_host.validate_image_usbkey() != build:
191*9c5db199SXin Li            raise hosts.AutoservRepairError('%s is expected to be preloaded,'
192*9c5db199SXin Li                      'however it\'s not found on the usbkey' % build,
193*9c5db199SXin Li                      'image not loaded on usbkey')
194*9c5db199SXin Li        ec_image, bios_image = host._servo_host.prepare_repair_firmware_image()
195*9c5db199SXin Li
196*9c5db199SXin Li        # For EVT device with signed variant exists we skip this repair
197*9c5db199SXin Li        # as it's hard to decide which image to use if DUT do not boot.
198*9c5db199SXin Li        info = host.host_info_store.get()
199*9c5db199SXin Li        phase = info.get_label_value('phase')
200*9c5db199SXin Li        if 'signed' in bios_image and phase.lower() in ('evt', 'dvt', ''):
201*9c5db199SXin Li            raise hosts.AutoservRepairError(
202*9c5db199SXin Li                    'Could not determine which firmware image to use'
203*9c5db199SXin Li                    ' due to signed firmware image variant exists but'
204*9c5db199SXin Li                    ' DUT phase is earlier than PVT or missing; Phase'
205*9c5db199SXin Li                    ' from inventory: %s' % phase,
206*9c5db199SXin Li                    'Can not determine variant for EVT device')
207*9c5db199SXin Li
208*9c5db199SXin Li        # Before flash firmware we want update the build into health profile.
209*9c5db199SXin Li        if host.health_profile:
210*9c5db199SXin Li            host.health_profile.set_firmware_stable_version(build)
211*9c5db199SXin Li
212*9c5db199SXin Li        if ec_image:
213*9c5db199SXin Li            logging.info('Attempting to flash ec firmware...')
214*9c5db199SXin Li            host.servo.program_ec(ec_image, copy_image=False)
215*9c5db199SXin Li        if bios_image:
216*9c5db199SXin Li            logging.info('Attempting to flash bios firmware...')
217*9c5db199SXin Li            host._servo_host.flash_ap_firmware_via_servo(bios_image)
218*9c5db199SXin Li
219*9c5db199SXin Li        logging.info('Cold resetting DUT through servo...')
220*9c5db199SXin Li        host.servo.get_power_state_controller().reset()
221*9c5db199SXin Li        host.wait_up(timeout=host.BOOT_TIMEOUT)
222*9c5db199SXin Li        # flash firmware via servo will turn DUT into dev mode, so disable
223*9c5db199SXin Li        # dev mode and reset gbb flag here.
224*9c5db199SXin Li        host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0', ignore_status=True)
225*9c5db199SXin Li        host.run('crossystem disable_dev_request=1', ignore_status=True)
226*9c5db199SXin Li        host.reboot()
227*9c5db199SXin Li
228*9c5db199SXin Li
229*9c5db199SXin Liclass FaftFirmwareRepair(FirmwareRepair):
230*9c5db199SXin Li    """
231*9c5db199SXin Li    Reinstall the firmware for DUTs in faft related pool.
232*9c5db199SXin Li    """
233*9c5db199SXin Li
234*9c5db199SXin Li    def repair(self, host):
235*9c5db199SXin Li        repair_utils.require_servo(host, ignore_state=True)
236*9c5db199SXin Li        build = self._get_faft_stable_build(host)
237*9c5db199SXin Li        if build:
238*9c5db199SXin Li            self._run_faft_repair(host, build)
239*9c5db199SXin Li        else:
240*9c5db199SXin Li            logging.info('Cannot find faft stable_version, falling back to'
241*9c5db199SXin Li                         ' use firmware on OS stable_version.')
242*9c5db199SXin Li            build = self._get_os_stable_build(host)
243*9c5db199SXin Li            if not build:
244*9c5db199SXin Li                raise hosts.AutoservRepairError(
245*9c5db199SXin Li                        'Failed to find stable_version from host_info.',
246*9c5db199SXin Li                        'cannot find stable_version')
247*9c5db199SXin Li            self._run_general_repair(host, build)
248*9c5db199SXin Li
249*9c5db199SXin Li    def _is_applicable(self, host):
250*9c5db199SXin Li        return _is_firmware_testing_device(host)
251*9c5db199SXin Li
252*9c5db199SXin Li    @property
253*9c5db199SXin Li    def description(self):
254*9c5db199SXin Li        return 'Re-install the stable firmware(faft) via servo'
255*9c5db199SXin Li
256*9c5db199SXin Li
257*9c5db199SXin Liclass GeneralFirmwareRepair(FirmwareRepair):
258*9c5db199SXin Li    """Reinstall the firmware for non-faft DUTs.
259*9c5db199SXin Li    We need different RepairAction for non firmware testing DUT because
260*9c5db199SXin Li    we want only try re-install firmware if all other RepairAction could
261*9c5db199SXin Li    not restore ssh capability to the DUT.
262*9c5db199SXin Li    """
263*9c5db199SXin Li
264*9c5db199SXin Li    def repair(self, host):
265*9c5db199SXin Li        repair_utils.require_servo(host, ignore_state=True)
266*9c5db199SXin Li        build = self._get_os_stable_build(host)
267*9c5db199SXin Li        if not build:
268*9c5db199SXin Li            raise hosts.AutoservRepairError(
269*9c5db199SXin Li                    'Failed to find stable_version from host_info.',
270*9c5db199SXin Li                    'cannot find stable_version')
271*9c5db199SXin Li        self._run_general_repair(host, build)
272*9c5db199SXin Li
273*9c5db199SXin Li    def _is_applicable(self, host):
274*9c5db199SXin Li        if _is_firmware_testing_device(host):
275*9c5db199SXin Li            return False
276*9c5db199SXin Li        if not host.servo:
277*9c5db199SXin Li            logging.info(
278*9c5db199SXin Li                    'The current servo state of %s is not met the'
279*9c5db199SXin Li                    ' minimum requirement to flash firmware.', host.hostname)
280*9c5db199SXin Li        # Flash firmware via servo is consider an expansive opertation, so we
281*9c5db199SXin Li        # want to check repair data from previous repairs to determine if
282*9c5db199SXin Li        # firmware repair is need.
283*9c5db199SXin Li        dhp = host.health_profile
284*9c5db199SXin Li        if not dhp:
285*9c5db199SXin Li            logging.info('Device health profile is not available, cannot'
286*9c5db199SXin Li                         ' determine if firmware repair is needed.')
287*9c5db199SXin Li            return False
288*9c5db199SXin Li        repair_fail_count = dhp.get_repair_fail_count()
289*9c5db199SXin Li        if repair_fail_count < 2:
290*9c5db199SXin Li            # We want to start with a more conservative strategy, so only try
291*9c5db199SXin Li            # this action on DUTs that failed repair at least twice.
292*9c5db199SXin Li            # @TODO(xianuowang@) adjust or remove this threshold.
293*9c5db199SXin Li            logging.info(
294*9c5db199SXin Li                    'Firmware repair will only applies to DUT that'
295*9c5db199SXin Li                    ' failed at least two AdminRepair, current fail'
296*9c5db199SXin Li                    ' count: %s', repair_fail_count)
297*9c5db199SXin Li            return False
298*9c5db199SXin Li        flashed_build = dhp.get_firmware_stable_version()
299*9c5db199SXin Li        candidate_build = self._get_os_stable_build(host)
300*9c5db199SXin Li        # If we had an success firmware flash in this repair loop,
301*9c5db199SXin Li        # there is no need to retry flash the same firmware build.
302*9c5db199SXin Li        if (dhp.get_succeed_repair_action(self.tag) > 0
303*9c5db199SXin Li                    and flashed_build == candidate_build):
304*9c5db199SXin Li            logging.info(
305*9c5db199SXin Li                    'Firmware from %s has been already installed on %s,'
306*9c5db199SXin Li                    ' no need to retry.', flashed_build, host.hostname)
307*9c5db199SXin Li            return False
308*9c5db199SXin Li        if (dhp.get_failed_repair_action(self.tag) > 2
309*9c5db199SXin Li                    and flashed_build == candidate_build):
310*9c5db199SXin Li            logging.info(
311*9c5db199SXin Li                    'Firmware from %s has been attempted and failed 3 '
312*9c5db199SXin Li                    'times, no need to retry.', flashed_build)
313*9c5db199SXin Li            return False
314*9c5db199SXin Li        return True
315*9c5db199SXin Li
316*9c5db199SXin Li    @property
317*9c5db199SXin Li    def description(self):
318*9c5db199SXin Li        return 'Re-install the stable firmware(non-faft) via servo'
319*9c5db199SXin Li
320*9c5db199SXin Li
321*9c5db199SXin Liclass FirmwareVersionVerifier(hosts.Verifier):
322*9c5db199SXin Li    """
323*9c5db199SXin Li    Check for a firmware update, and apply it if appropriate.
324*9c5db199SXin Li
325*9c5db199SXin Li    This verifier checks to ensure that either the firmware on the DUT
326*9c5db199SXin Li    is up-to-date, or that the target firmware can be installed from the
327*9c5db199SXin Li    currently running build.
328*9c5db199SXin Li
329*9c5db199SXin Li    Failure occurs when all of the following apply:
330*9c5db199SXin Li     1. The DUT is not excluded from updates.  For example, DUTs used
331*9c5db199SXin Li        for FAFT testing use `FirmwareRepair` instead.
332*9c5db199SXin Li     2. The DUT's board has an assigned stable firmware version.
333*9c5db199SXin Li     3. The DUT is not running the assigned stable firmware.
334*9c5db199SXin Li     4. The firmware supplied in the running OS build is not the
335*9c5db199SXin Li        assigned stable firmware.
336*9c5db199SXin Li
337*9c5db199SXin Li    If the DUT needs an upgrade and the currently running OS build
338*9c5db199SXin Li    supplies the necessary firmware, the verifier installs the new
339*9c5db199SXin Li    firmware using `chromeos-firmwareupdate`.  Failure to install will
340*9c5db199SXin Li    cause the verifier to fail.
341*9c5db199SXin Li
342*9c5db199SXin Li    This verifier nominally breaks the rule that "verifiers must succeed
343*9c5db199SXin Li    quickly", since it can invoke `reboot()` during the success code
344*9c5db199SXin Li    path.  We're doing it anyway for two reasons:
345*9c5db199SXin Li      * The time between updates will typically be measured in months,
346*9c5db199SXin Li        so the amortized cost is low.
347*9c5db199SXin Li      * The reason we distinguish repair from verify is to allow
348*9c5db199SXin Li        rescheduling work immediately while the expensive repair happens
349*9c5db199SXin Li        out-of-band.  But a firmware update will likely hit all DUTs at
350*9c5db199SXin Li        once, so it's pointless to pass the buck to repair.
351*9c5db199SXin Li
352*9c5db199SXin Li    N.B. This verifier is a trigger for all repair actions that install
353*9c5db199SXin Li    the stable repair image.  If the firmware is out-of-date, but the
354*9c5db199SXin Li    stable repair image does *not* contain the proper firmware version,
355*9c5db199SXin Li    _the target DUT will fail repair, and will be unable to fix itself_.
356*9c5db199SXin Li    """
357*9c5db199SXin Li
358*9c5db199SXin Li    @staticmethod
359*9c5db199SXin Li    def _get_rw_firmware(host):
360*9c5db199SXin Li        result = host.run('crossystem fwid', ignore_status=True)
361*9c5db199SXin Li        if result.exit_status == 0:
362*9c5db199SXin Li            return result.stdout
363*9c5db199SXin Li        else:
364*9c5db199SXin Li            return None
365*9c5db199SXin Li
366*9c5db199SXin Li    @staticmethod
367*9c5db199SXin Li    def _check_hardware_match(version_a, version_b):
368*9c5db199SXin Li        """
369*9c5db199SXin Li        Check that two firmware versions identify the same hardware.
370*9c5db199SXin Li
371*9c5db199SXin Li        Firmware version strings look like this:
372*9c5db199SXin Li            Google_Gnawty.5216.239.34
373*9c5db199SXin Li        The part before the numbers identifies the hardware for which
374*9c5db199SXin Li        the firmware was built.  This function checks that the hardware
375*9c5db199SXin Li        identified by `version_a` and `version_b` is the same.
376*9c5db199SXin Li
377*9c5db199SXin Li        This is a confidence check to protect us from installing the wrong
378*9c5db199SXin Li        firmware on a DUT when a board label has somehow gone astray.
379*9c5db199SXin Li
380*9c5db199SXin Li        @param version_a  First firmware version for the comparison.
381*9c5db199SXin Li        @param version_b  Second firmware version for the comparison.
382*9c5db199SXin Li        """
383*9c5db199SXin Li        hardware_a = version_a.split('.')[0]
384*9c5db199SXin Li        hardware_b = version_b.split('.')[0]
385*9c5db199SXin Li        if hardware_a != hardware_b:
386*9c5db199SXin Li            message = 'Hardware/Firmware mismatch updating %s to %s'
387*9c5db199SXin Li            raise hosts.AutoservVerifyError(
388*9c5db199SXin Li                    message % (version_a, version_b))
389*9c5db199SXin Li
390*9c5db199SXin Li    def _is_stable_image_installed(self, host):
391*9c5db199SXin Li        """Verify that ChromeOS image on host is a stable version.
392*9c5db199SXin Li
393*9c5db199SXin Li        This check verify that device booted from stable image to protect us
394*9c5db199SXin Li        from installing the firmware from bad/broken/no-tested image. Bad
395*9c5db199SXin Li        image can have broken updater or corrupted firmware.
396*9c5db199SXin Li
397*9c5db199SXin Li        The representation version looks like:
398*9c5db199SXin Li                nocturne-release/R89-13728.0.0
399*9c5db199SXin Li        Check compare version from host to version provide as stable image
400*9c5db199SXin Li        from host-info file.
401*9c5db199SXin Li
402*9c5db199SXin Li        @param host  CrosHost instance.
403*9c5db199SXin Li        """
404*9c5db199SXin Li        os_from_host = host.get_release_builder_path()
405*9c5db199SXin Li        os_from_host_info = host.get_cros_repair_image_name()
406*9c5db199SXin Li        if os_from_host != os_from_host_info:
407*9c5db199SXin Li            raise hosts.AutoservNonCriticalVerifyError(
408*9c5db199SXin Li                    'Firmware update can be run only from stable image.'
409*9c5db199SXin Li                    ' Expected version:"%s", actually: "%s"' %
410*9c5db199SXin Li                    (os_from_host_info, os_from_host))
411*9c5db199SXin Li
412*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
413*9c5db199SXin Li    def verify(self, host):
414*9c5db199SXin Li        # Test 1 - The DUT is not excluded from updates.
415*9c5db199SXin Li        if not _is_firmware_update_supported(host):
416*9c5db199SXin Li            return
417*9c5db199SXin Li        # Test 2 - The DUT has an assigned stable firmware version.
418*9c5db199SXin Li        info = host.host_info_store.get()
419*9c5db199SXin Li        if info.model is None:
420*9c5db199SXin Li            raise hosts.AutoservVerifyError(
421*9c5db199SXin Li                    'Can not verify firmware version. '
422*9c5db199SXin Li                    'No model label value found')
423*9c5db199SXin Li
424*9c5db199SXin Li        stable_firmware = None
425*9c5db199SXin Li        try:
426*9c5db199SXin Li            stable_firmware = afe_utils.get_stable_firmware_version_v2(info)
427*9c5db199SXin Li        except Exception as e:
428*9c5db199SXin Li            logging.exception('Failed lookup to AFE for stable fw version '
429*9c5db199SXin Li                              ' with exception: %s', e)
430*9c5db199SXin Li
431*9c5db199SXin Li        if stable_firmware is None:
432*9c5db199SXin Li            logging.debug('Expected FW version not found')
433*9c5db199SXin Li            # This DUT doesn't have a firmware update target
434*9c5db199SXin Li            return
435*9c5db199SXin Li        logging.debug('Expected FW version: %s', stable_firmware)
436*9c5db199SXin Li        # For tests 3 and 4:  If the output from `crossystem` or
437*9c5db199SXin Li        # `chromeos-firmwareupdate` isn't what we expect, we log an
438*9c5db199SXin Li        # error, but don't fail:  We don't want DUTs unable to test a
439*9c5db199SXin Li        # build merely because of a bug or change in either of those
440*9c5db199SXin Li        # commands.
441*9c5db199SXin Li
442*9c5db199SXin Li        # Test 3 - The DUT is not running the target stable firmware.
443*9c5db199SXin Li        current_firmware = self._get_rw_firmware(host)
444*9c5db199SXin Li        if current_firmware is None:
445*9c5db199SXin Li            logging.error('DUT firmware version can\'t be determined.')
446*9c5db199SXin Li            return
447*9c5db199SXin Li        logging.debug('Current FW version: %s', current_firmware)
448*9c5db199SXin Li        if current_firmware == stable_firmware:
449*9c5db199SXin Li            return
450*9c5db199SXin Li        # Test 4 - The firmware supplied in the running OS build is not
451*9c5db199SXin Li        # the assigned stable firmware.
452*9c5db199SXin Li        available_firmware = _get_available_firmware(host, info.model)
453*9c5db199SXin Li        if available_firmware is None:
454*9c5db199SXin Li            logging.error('Supplied firmware version in OS can\'t be '
455*9c5db199SXin Li                          'determined.')
456*9c5db199SXin Li            return
457*9c5db199SXin Li        self._is_stable_image_installed(host)
458*9c5db199SXin Li        if available_firmware != stable_firmware:
459*9c5db199SXin Li            raise hosts.AutoservVerifyError(
460*9c5db199SXin Li                    'DUT firmware requires update from %s to %s' %
461*9c5db199SXin Li                    (current_firmware, stable_firmware))
462*9c5db199SXin Li        # Time to update the firmware.
463*9c5db199SXin Li        logging.info('Updating firmware from %s to %s',
464*9c5db199SXin Li                     current_firmware, stable_firmware)
465*9c5db199SXin Li        self._check_hardware_match(current_firmware, stable_firmware)
466*9c5db199SXin Li        try:
467*9c5db199SXin Li            host.run('chromeos-firmwareupdate --mode=autoupdate')
468*9c5db199SXin Li            host.reboot()
469*9c5db199SXin Li        except Exception as e:
470*9c5db199SXin Li            message = ('chromeos-firmwareupdate failed: from '
471*9c5db199SXin Li                       '%s to %s')
472*9c5db199SXin Li            logging.exception(message, current_firmware, stable_firmware)
473*9c5db199SXin Li            raise hosts.AutoservVerifyError(
474*9c5db199SXin Li                    message % (current_firmware, stable_firmware))
475*9c5db199SXin Li        final_firmware = self._get_rw_firmware(host)
476*9c5db199SXin Li        if final_firmware != stable_firmware:
477*9c5db199SXin Li            message = ('chromeos-firmwareupdate failed: tried upgrade '
478*9c5db199SXin Li                       'to %s, now running %s instead')
479*9c5db199SXin Li            raise hosts.AutoservVerifyError(
480*9c5db199SXin Li                    message % (stable_firmware, final_firmware))
481*9c5db199SXin Li
482*9c5db199SXin Li    @property
483*9c5db199SXin Li    def description(self):
484*9c5db199SXin Li        return 'The firmware on this DUT is up-to-date'
485