• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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
5import logging
6import os
7
8from chromite.lib import remote_access
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib import utils
11from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
12
13
14class firmware_FWupdate(FirmwareTest):
15    """RO+RW firmware update using chromeos-firmware with various modes.
16    If custom images are supplied, the DUT is left running that firmware, so the
17    test can be used to apply updates.  Otherwise, it modifies the FWIDs of the
18    current firmware before flashing, and restores the firmware after the test.
19
20    Accepted --args names:
21
22    mode=[recovery|factory]
23        Run test with the given mode (default 'recovery')
24
25    new_bios=
26    new_ec=
27    new_pd=
28        apply the given image(s) instead of generating an update with fake fwids
29
30    """
31
32    # Region to use for flashrom wp-region commands
33    WP_REGION = 'WP_RO'
34
35    def initialize(self, host, cmdline_args):
36
37        self.images_specified = False
38        self.flashed = False
39
40        dict_args = utils.args_to_dict(cmdline_args)
41        super(firmware_FWupdate, self).initialize(host, cmdline_args)
42
43        self.new_bios = dict_args.get('new_bios', None)
44        self.new_ec = dict_args.get('new_ec', None)
45        self.new_pd = dict_args.get('new_pd', None)
46
47        if self.new_bios:
48            self.images_specified = True
49            if not os.path.isfile(self.new_bios):
50                raise error.TestError('Specified BIOS file does not exist: %s'
51                                      % self.new_bios)
52            logging.info('new_bios=%s', self.new_bios)
53
54        if self.new_ec:
55            self.images_specified = True
56            if not os.path.isfile(self.new_ec):
57                raise error.TestError('Specified EC file does not exist: %s'
58                                      % self.new_ec)
59            logging.info('new_ec=%s', self.new_ec)
60
61        if self.new_pd:
62            self.images_specified = True
63            if not os.path.isfile(self.new_pd):
64                raise error.TestError('Specified PD file does not exist: %s'
65                                      % self.new_pd)
66            logging.info('new_pd=%s', self.new_pd)
67
68        self._old_bios_wp = self.faft_client.bios.get_write_protect_status()
69
70        if not self.images_specified:
71            # TODO(dgoyette): move this into the general FirmwareTest init?
72            stripped_bios = self.faft_client.bios.strip_modified_fwids()
73            if stripped_bios:
74                logging.warn(
75                        "Fixed the previously modified BIOS FWID(s): %s",
76                        stripped_bios)
77
78            if self.faft_config.chrome_ec:
79                stripped_ec = self.faft_client.ec.strip_modified_fwids()
80                if stripped_ec:
81                    logging.warn(
82                            "Fixed the previously modified EC FWID(s): %s",
83                            stripped_ec)
84
85            self.backup_firmware()
86
87        if 'wp' in dict_args:
88            self.wp = int(dict_args['wp'])
89        else:
90            self.wp = None
91
92        self.set_hardware_write_protect(False)
93        self.faft_client.bios.set_write_protect_region(self.WP_REGION, True)
94        self.set_hardware_write_protect(True)
95
96        self.mode = dict_args.get('mode', 'recovery')
97
98        if self.mode not in ('factory', 'recovery'):
99            raise error.TestError('Unhandled mode: %s' % self.mode)
100
101        if self.mode == 'factory' and self.wp:
102            # firmware_UpdateModes already checks this case, so skip it here.
103            raise error.TestNAError(
104                    "This test doesn't handle mode=factory with wp=1")
105
106    def get_installed_versions(self):
107        """Get the installed versions of BIOS and EC firmware.
108
109        @return: A nested dict keyed by target ('bios' or 'ec') and then section
110        @rtype: dict
111        """
112        versions = dict()
113        versions['bios'] = self.faft_client.updater.get_all_installed_fwids(
114                'bios')
115        if self.faft_config.chrome_ec:
116            versions['ec'] = self.faft_client.updater.get_all_installed_fwids(
117                    'ec')
118        return versions
119
120    def copy_cmdline_images(self, hostname):
121        """Copy the specified command line images into the extracted shellball.
122
123        @param hostname: hostname (not the Host object) to copy to
124        """
125        if self.new_bios or self.new_ec or self.new_pd:
126
127            extract_dir = self.faft_client.updater.get_work_path()
128
129            dut_access = remote_access.RemoteDevice(hostname, username='root')
130
131            # Replace bin files.
132            if self.new_bios:
133                bios_rel = self.faft_client.updater.get_bios_relative_path()
134                bios_path = os.path.join(extract_dir, bios_rel)
135                dut_access.CopyToDevice(self.new_bios, bios_path, mode='scp')
136
137            if self.new_ec:
138                ec_rel = self.faft_client.updater.get_ec_relative_path()
139                ec_path = os.path.join(extract_dir, ec_rel)
140                dut_access.CopyToDevice(self.new_ec, ec_path, mode='scp')
141
142            if self.new_pd:
143                # note: pd.bin might likewise need special path logic
144                pd_path = os.path.join(extract_dir, 'pd.bin')
145                dut_access.CopyToDevice(self.new_pd, pd_path, mode='scp')
146
147    def run_case(self, append, write_protected, before_fwids, modded_fwids):
148        """Run chromeos-firmwareupdate with given sub-case
149
150        @param append: additional piece to add to shellball name
151        @param write_protected: is the flash write protected (--wp)?
152        @param before_fwids: fwids before flashing ('bios' and 'ec' as keys)
153        @param modded_fwids: fwids in image ('bios' and 'ec' as keys)
154        @return: a list of failure messages for the case
155        """
156
157        cmd_desc = ('chromeos-firmwareupdate --mode=%s [wp=%s]'
158                    % (self.mode, write_protected))
159
160        # Unlock the protection of the wp-enable and wp-range registers
161        self.set_hardware_write_protect(False)
162
163        if write_protected:
164            self.faft_client.bios.set_write_protect_region(self.WP_REGION, True)
165            self.set_hardware_write_protect(True)
166        else:
167            self.faft_client.bios.set_write_protect_region(
168                    self.WP_REGION, False)
169
170        expected_written = {}
171        written_desc = []
172
173        if write_protected:
174            bios_written = ['a', 'b']
175            ec_written = []  # EC write is all-or-nothing
176
177        else:
178            bios_written = ['ro', 'a', 'b']
179            ec_written = ['ro', 'rw']
180
181        expected_written['bios'] = bios_written
182        written_desc += ['bios %s' % '+'.join(bios_written)]
183
184        if self.faft_config.chrome_ec and ec_written:
185            expected_written['ec'] = ec_written
186            written_desc += ['ec %s' % '+'.join(ec_written)]
187
188        written_desc = '(should write %s)' % ', '.join(written_desc)
189        logging.info("Run %s %s", cmd_desc, written_desc)
190
191        # make sure we restore firmware after the test, if it tried to flash.
192        self.flashed = True
193        self.faft_client.updater.run_firmwareupdate(self.mode, append)
194
195        after_fwids = self.get_installed_versions()
196
197        errors = self.check_fwids_written(
198                before_fwids, modded_fwids, after_fwids, expected_written)
199
200        if errors:
201            logging.debug('%s', '\n'.join(errors))
202            return ["%s: %s\n%s" % (cmd_desc, written_desc, '\n'.join(errors))]
203        else:
204            return []
205
206    def run_once(self, host):
207        """Run chromeos-firmwareupdate with recovery or factory mode.
208
209        @param host: host to run on
210        """
211        append = 'new'
212        have_ec = bool(self.faft_config.chrome_ec)
213
214        self.faft_client.updater.extract_shellball()
215
216        before_fwids = self.get_installed_versions()
217
218        # Repack shellball with modded fwids
219        if self.images_specified:
220            # Use new images as-is
221            logging.info(
222                    "Using specified image(s):"
223                    "new_bios=%s, new_ec=%s, new_pd=%s",
224                    self.new_bios, self.new_ec, self.new_pd)
225            self.copy_cmdline_images(host.hostname)
226            self.faft_client.updater.reload_images()
227            self.faft_client.updater.repack_shellball(append)
228            modded_fwids = self.identify_shellball(include_ec=have_ec)
229        else:
230            # Modify the stock image
231            logging.info(
232                    "Using the currently running firmware, with modified fwids")
233            self.setup_firmwareupdate_shellball()
234            self.faft_client.updater.reload_images()
235            self.modify_shellball(append, modify_ro=True, modify_ec=have_ec)
236            modded_fwids = self.identify_shellball(include_ec=have_ec)
237
238        fail_msg = "Section contents didn't show the expected changes."
239
240        errors = []
241        if self.wp is not None:
242            # try only the specified wp= value
243            errors += self.run_case(append, self.wp, before_fwids, modded_fwids)
244
245        elif self.images_specified or self.mode == 'factory':
246            # apply images with wp=0 by default
247            errors += self.run_case(append, 0, before_fwids, modded_fwids)
248
249        else:
250            # no args specified, so check both wp=1 and wp=0
251            errors += self.run_case(append, 1, before_fwids, modded_fwids)
252            errors += self.run_case(append, 0, before_fwids, modded_fwids)
253
254        if errors:
255            raise error.TestFail("%s\n%s" % (fail_msg, '\n'.join(errors)))
256
257    def cleanup(self):
258        """
259        If test was given custom images to apply, reboot the EC to apply them.
260
261        Otherwise, restore firmware from the backup taken before flashing.
262        No EC reboot is needed in that case, because the test didn't actually
263        reboot the EC with the new firmware.
264        """
265        self.set_hardware_write_protect(False)
266        self.faft_client.bios.set_write_protect_range(0, 0, False)
267
268        if self.flashed:
269            if self.images_specified:
270                self.sync_and_ec_reboot('hard')
271            else:
272                logging.info("Restoring firmware")
273                self.restore_firmware()
274
275        # Restore the old write-protection value at the end of the test.
276        self.faft_client.bios.set_write_protect_range(
277                self._old_bios_wp['start'],
278                self._old_bios_wp['length'],
279                self._old_bios_wp['enabled'])
280
281        super(firmware_FWupdate, self).cleanup()
282