| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| |
| from chromite.lib import remote_access |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| |
| |
| class firmware_FWupdate(FirmwareTest): |
| """RO+RW firmware update using chromeos-firmware with various modes. |
| If custom images are supplied, the DUT is left running that firmware, so the |
| test can be used to apply updates. Otherwise, it modifies the FWIDs of the |
| current firmware before flashing, and restores the firmware after the test. |
| |
| Accepted --args names: |
| |
| mode=[recovery|factory] |
| Run test with the given mode (default 'recovery') |
| |
| new_bios= |
| new_ec= |
| new_pd= |
| apply the given image(s) instead of generating an update with fake fwids |
| |
| """ |
| |
| # Region to use for flashrom wp-region commands |
| WP_REGION = 'WP_RO' |
| |
| def initialize(self, host, cmdline_args): |
| |
| self.images_specified = False |
| self.flashed = False |
| |
| dict_args = utils.args_to_dict(cmdline_args) |
| super(firmware_FWupdate, self).initialize(host, cmdline_args) |
| |
| self.new_bios = dict_args.get('new_bios', None) |
| self.new_ec = dict_args.get('new_ec', None) |
| self.new_pd = dict_args.get('new_pd', None) |
| |
| if self.new_bios: |
| self.images_specified = True |
| if not os.path.isfile(self.new_bios): |
| raise error.TestError('Specified BIOS file does not exist: %s' |
| % self.new_bios) |
| logging.info('new_bios=%s', self.new_bios) |
| |
| if self.new_ec: |
| self.images_specified = True |
| if not os.path.isfile(self.new_ec): |
| raise error.TestError('Specified EC file does not exist: %s' |
| % self.new_ec) |
| logging.info('new_ec=%s', self.new_ec) |
| |
| if self.new_pd: |
| self.images_specified = True |
| if not os.path.isfile(self.new_pd): |
| raise error.TestError('Specified PD file does not exist: %s' |
| % self.new_pd) |
| logging.info('new_pd=%s', self.new_pd) |
| |
| self._old_bios_wp = self.faft_client.Bios.GetWriteProtectStatus() |
| |
| if not self.images_specified: |
| # TODO(dgoyette): move this into the general FirmwareTest init? |
| stripped_bios = self.faft_client.Bios.StripModifiedFwids() |
| if stripped_bios: |
| logging.warn( |
| "Fixed the previously modified BIOS FWID(s): %s", |
| stripped_bios) |
| |
| if self.faft_config.chrome_ec: |
| stripped_ec = self.faft_client.Ec.StripModifiedFwids() |
| if stripped_ec: |
| logging.warn( |
| "Fixed the previously modified EC FWID(s): %s", |
| stripped_ec) |
| |
| self.backup_firmware() |
| |
| if 'wp' in dict_args: |
| self.wp = int(dict_args['wp']) |
| else: |
| self.wp = None |
| |
| self.set_hardware_write_protect(False) |
| self.faft_client.Bios.SetWriteProtectRegion(self.WP_REGION, True) |
| self.set_hardware_write_protect(True) |
| |
| self.mode = dict_args.get('mode', 'recovery') |
| |
| if self.mode not in ('factory', 'recovery'): |
| raise error.TestError('Unhandled mode: %s' % self.mode) |
| |
| if self.mode == 'factory' and self.wp: |
| # firmware_UpdateModes already checks this case, so skip it here. |
| raise error.TestNAError( |
| "This test doesn't handle mode=factory with wp=1") |
| |
| def get_installed_versions(self): |
| """Get the installed versions of BIOS and EC firmware. |
| |
| @return: A nested dict keyed by target ('bios' or 'ec') and then section |
| @rtype: dict |
| """ |
| versions = dict() |
| versions['bios'] = self.faft_client.Updater.GetAllInstalledFwids('bios') |
| if self.faft_config.chrome_ec: |
| versions['ec'] = self.faft_client.Updater.GetAllInstalledFwids('ec') |
| return versions |
| |
| def copy_cmdline_images(self, hostname): |
| """Copy the specified command line images into the extracted shellball. |
| |
| @param hostname: hostname (not the Host object) to copy to |
| """ |
| if self.new_bios or self.new_ec or self.new_pd: |
| |
| extract_dir = self.faft_client.Updater.GetWorkPath() |
| |
| dut_access = remote_access.RemoteDevice(hostname, username='root') |
| |
| # Replace bin files. |
| if self.new_bios: |
| bios_rel = self.faft_client.Updater.GetBiosRelativePath() |
| bios_path = os.path.join(extract_dir, bios_rel) |
| dut_access.CopyToDevice(self.new_bios, bios_path, mode='scp') |
| |
| if self.new_ec: |
| ec_rel = self.faft_client.Updater.GetEcRelativePath() |
| ec_path = os.path.join(extract_dir, ec_rel) |
| dut_access.CopyToDevice(self.new_ec, ec_path, mode='scp') |
| |
| if self.new_pd: |
| # note: pd.bin might likewise need special path logic |
| pd_path = os.path.join(extract_dir, 'pd.bin') |
| dut_access.CopyToDevice(self.new_pd, pd_path, mode='scp') |
| |
| def run_case(self, append, write_protected, before_fwids, modded_fwids): |
| """Run chromeos-firmwareupdate with given sub-case |
| |
| @param append: additional piece to add to shellball name |
| @param write_protected: is the flash write protected (--wp)? |
| @param before_fwids: fwids before flashing ('bios' and 'ec' as keys) |
| @param modded_fwids: fwids in image ('bios' and 'ec' as keys) |
| @return: a list of failure messages for the case |
| """ |
| |
| cmd_desc = ('chromeos-firmwareupdate --mode=%s [wp=%s]' |
| % (self.mode, write_protected)) |
| |
| # Unlock the protection of the wp-enable and wp-range registers |
| self.set_hardware_write_protect(False) |
| |
| if write_protected: |
| self.faft_client.Bios.SetWriteProtectRegion(self.WP_REGION, True) |
| self.set_hardware_write_protect(True) |
| else: |
| self.faft_client.Bios.SetWriteProtectRegion(self.WP_REGION, False) |
| |
| expected_written = {} |
| written_desc = [] |
| |
| if write_protected: |
| bios_written = ['a', 'b'] |
| ec_written = [] # EC write is all-or-nothing |
| |
| else: |
| bios_written = ['ro', 'a', 'b'] |
| ec_written = ['ro', 'rw'] |
| |
| expected_written['bios'] = bios_written |
| written_desc += ['bios %s' % '+'.join(bios_written)] |
| |
| if self.faft_config.chrome_ec and ec_written: |
| expected_written['ec'] = ec_written |
| written_desc += ['ec %s' % '+'.join(ec_written)] |
| |
| written_desc = '(should write %s)' % ', '.join(written_desc) |
| logging.info("Run %s %s", cmd_desc, written_desc) |
| |
| # make sure we restore firmware after the test, if it tried to flash. |
| self.flashed = True |
| self.faft_client.Updater.RunFirmwareupdate(self.mode, append) |
| |
| after_fwids = self.get_installed_versions() |
| |
| errors = self.check_fwids_written( |
| before_fwids, modded_fwids, after_fwids, expected_written) |
| |
| if errors: |
| logging.debug('%s', '\n'.join(errors)) |
| return ["%s: %s\n%s" % (cmd_desc, written_desc, '\n'.join(errors))] |
| else: |
| return [] |
| |
| def run_once(self, host): |
| """Run chromeos-firmwareupdate with recovery or factory mode. |
| |
| @param host: host to run on |
| """ |
| append = 'new' |
| have_ec = bool(self.faft_config.chrome_ec) |
| |
| self.faft_client.Updater.ExtractShellball() |
| |
| before_fwids = self.get_installed_versions() |
| |
| # Repack shellball with modded fwids |
| if self.images_specified: |
| # Use new images as-is |
| logging.info( |
| "Using specified image(s):" |
| "new_bios=%s, new_ec=%s, new_pd=%s", |
| self.new_bios, self.new_ec, self.new_pd) |
| self.copy_cmdline_images(host.hostname) |
| self.faft_client.Updater.ReloadImages() |
| self.faft_client.Updater.RepackShellball(append) |
| modded_fwids = self.identify_shellball(include_ec=have_ec) |
| else: |
| # Modify the stock image |
| logging.info( |
| "Using the currently running firmware, with modified fwids") |
| self.setup_firmwareupdate_shellball() |
| self.faft_client.Updater.ReloadImages() |
| self.modify_shellball(append, modify_ro=True, modify_ec=have_ec) |
| modded_fwids = self.identify_shellball(include_ec=have_ec) |
| |
| fail_msg = "Section contents didn't show the expected changes." |
| |
| errors = [] |
| if self.wp is not None: |
| # try only the specified wp= value |
| errors += self.run_case(append, self.wp, before_fwids, modded_fwids) |
| |
| elif self.images_specified or self.mode == 'factory': |
| # apply images with wp=0 by default |
| errors += self.run_case(append, 0, before_fwids, modded_fwids) |
| |
| else: |
| # no args specified, so check both wp=1 and wp=0 |
| errors += self.run_case(append, 1, before_fwids, modded_fwids) |
| errors += self.run_case(append, 0, before_fwids, modded_fwids) |
| |
| if errors: |
| raise error.TestFail("%s\n%s" % (fail_msg, '\n'.join(errors))) |
| |
| def cleanup(self): |
| """ |
| If test was given custom images to apply, reboot the EC to apply them. |
| |
| Otherwise, restore firmware from the backup taken before flashing. |
| No EC reboot is needed in that case, because the test didn't actually |
| reboot the EC with the new firmware. |
| """ |
| self.set_hardware_write_protect(False) |
| self.faft_client.Bios.SetWriteProtectRange(0, 0, False) |
| |
| if self.flashed: |
| if self.images_specified: |
| self.sync_and_ec_reboot('hard') |
| else: |
| logging.info("Restoring firmware") |
| self.restore_firmware() |
| |
| # Restore the old write-protection value at the end of the test. |
| self.faft_client.Bios.SetWriteProtectRange( |
| self._old_bios_wp['start'], |
| self._old_bios_wp['length'], |
| self._old_bios_wp['enabled']) |
| |
| super(firmware_FWupdate, self).cleanup() |