xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_UpdaterModes/firmware_UpdaterModes.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Copyright 2019 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.
4import logging
5
6from autotest_lib.client.common_lib import error
7from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
8from autotest_lib.server.cros.faft.firmware_test import ConnectionError
9
10
11class firmware_UpdaterModes(FirmwareTest):
12    """RO+RW firmware update using chromeos-firmwareupdate with various modes.
13
14    This test uses --emulate, to avoid writing repeatedly to the flash.
15    """
16
17    version = 1
18
19    SHELLBALL = '/usr/sbin/chromeos-firmwareupdate'
20
21    def initialize(self, host, cmdline_args, ec_wp=None):
22        """
23        During initialization, back up the firmware, in case --emulate ever
24        breaks in a way that causes real writes.
25        """
26        super(firmware_UpdaterModes, self).initialize(host, cmdline_args, ec_wp)
27        self.backup_firmware()
28
29    def cleanup(self):
30        """Restore the original firmware, if it was somehow overwritten."""
31        try:
32            if self.is_firmware_saved():
33                self.restore_firmware()
34        except ConnectionError:
35            logging.error("ERROR: DUT did not come up after firmware restore!")
36        finally:
37            super(firmware_UpdaterModes, self).cleanup()
38
39    def get_bios_fwids(self, path):
40        """Return the BIOS fwids for the given file"""
41        return self.faft_client.updater.get_image_fwids('bios', path)
42
43    def run_case(self, mode, write_protected, written, modify_ro=True,
44            should_abort=False, writes_gbb=False):
45        """Run chromeos-firmwareupdate with given sub-case
46
47        @param mode: factory or recovery or autoupdate
48        @param write_protected: is the flash write protected (--wp)?
49        @param modify_ro: should ro fwid be modified?
50        @param written: list of bios areas expected to change
51        @param should_abort: if True, the updater should abort with no changes
52        @param writes_gbb: if True, the updater should rewrite gbb flags.
53        @return: a list of failure messages for the case
54        """
55        self.faft_client.updater.reset_shellball()
56
57        fake_bios_path = self.faft_client.updater.copy_bios('fake-bios.bin')
58        self.faft_client.updater.set_image_gbb_flags(0, fake_bios_path)
59
60        before_fwids = {'bios': self.get_bios_fwids(fake_bios_path)}
61        before_gbb = self.faft_client.updater.get_image_gbb_flags(
62                fake_bios_path)
63
64        case_desc = ('chromeos-firmwareupdate --mode=%s --wp=%s'
65                     % (mode, write_protected))
66
67        if modify_ro:
68            append = 'ro+rw'
69        else:
70            case_desc += ' [rw-only]'
71            append = 'rw'
72
73        # Repack the shellball with modded fwids
74        self.modify_shellball(append, modify_ro)
75        modded_fwids = self.identify_shellball()
76        image_gbb = self.faft_client.updater.get_image_gbb_flags()
77
78        options = ['--emulate', fake_bios_path, '--wp=%s' % write_protected]
79
80        logging.info("%s (should write %s)", case_desc,
81                     ', '.join(written).upper() or 'nothing')
82
83        errors = []
84        result = self.run_chromeos_firmwareupdate(mode, append, options,
85                                                  ignore_status=True)
86        if result.exit_status == 0:
87            if should_abort:
88                errors.append(
89                        "...updater: with current mode and write-protect value,"
90                        " should abort (rc!=0) and not modify anything")
91        else:
92            if should_abort:
93                logging.debug('updater aborted as expected')
94            else:
95                errors.append('...updater: unexpectedly failed (rc!=0)')
96
97        after_fwids = {'bios': self.get_bios_fwids(fake_bios_path)}
98        after_gbb = self.faft_client.updater.get_image_gbb_flags(fake_bios_path)
99        expected_written = {'bios': written or []}
100
101        errors += self.check_fwids_written(
102                before_fwids, modded_fwids, after_fwids, expected_written)
103
104        if not errors:
105            logging.debug('...bios versions correct: %s', after_fwids['bios'])
106
107        if writes_gbb:
108            if after_gbb != image_gbb:
109                # Expect rewritten, but it might not be different from before
110                errors.append(
111                        "...GBB flags weren't rewritten to match the image: "
112                        "before=0x%x, image=0x%x, after=0x%x."
113                        % (before_gbb, image_gbb, after_gbb))
114        else:
115            if after_gbb != before_gbb:
116                errors.append(
117                        "...GBB flags were unexpectedly rewritten: "
118                        "before=0x%x, image=0x%x, after=0x%x."
119                        % (before_gbb, image_gbb, after_gbb))
120
121        if self.restore_firmware():
122            # If real writes happen, fail immediately to avoid flash wear.
123            raise error.TestFail(
124                    'With chromeos-firmwareupdate --emulate, real flash device '
125                    'was unexpectedly modified.')
126
127        if errors:
128            case_message = '%s:\n%s' % (case_desc, '\n'.join(errors))
129            logging.error('%s', case_message)
130            return [case_message]
131        return []
132
133    def run_once(self, host):
134        """Run test, iterating through combinations of mode and write-protect"""
135        errors = []
136
137        # factory: update A, B, and RO; reset gbb flags.  If WP=1, abort.
138        errors += self.run_case('factory', 0, ['ro', 'a', 'b'], writes_gbb=True)
139        errors += self.run_case('factory', 1, [], should_abort=True)
140
141        # recovery: update A and B, and RO if WP=0.
142        errors += self.run_case('recovery', 0, ['ro', 'a', 'b'])
143        errors += self.run_case('recovery', 1, ['a', 'b'])
144
145        # autoupdate with changed ro: same as recovery (modify ro only if WP=0)
146        errors += self.run_case('autoupdate', 0, ['ro', 'a', 'b'])
147        errors += self.run_case('autoupdate', 1, ['b'])
148
149        # autoupdate with unchanged ro: update inactive slot
150        errors += self.run_case('autoupdate', 0, ['b'], modify_ro=False)
151        errors += self.run_case('autoupdate', 1, ['b'], modify_ro=False)
152
153        if len(errors) == 1:
154            raise error.TestFail(errors[0])
155        elif errors:
156            raise error.TestFail(
157                    '%s combinations of mode and write-protect failed:\n%s' %
158                    (len(errors), '\n'.join(errors)))
159