| # 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 |
| |
| """ |
| |
| 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) |
| |
| 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() |
| |
| self.set_hardware_write_protect(False) |
| |
| self.mode = dict_args.get('mode', 'recovery') |
| |
| if self.mode not in ('factory', 'recovery'): |
| raise error.TestError('Unhandled mode: %s' % self.mode) |
| |
| 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_once(self, host): |
| """Run chromeos-firmwareupdate with recovery or factory mode. |
| |
| @param host: host to run on |
| """ |
| mode = self.mode |
| 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) |
| |
| case_desc = 'chromeos-firmwareupdate --mode=%s --wp=0' % mode |
| |
| logging.info("Run %s", case_desc) |
| |
| # make sure we restore firmware after the test, if it tried to flash. |
| self.flashed = True |
| self.faft_client.Updater.RunFirmwareupdate(mode, append, ['--wp=0']) |
| |
| after_fwids = self.get_installed_versions() |
| |
| errors = self.check_fwids_written( |
| before_fwids, modded_fwids, after_fwids, |
| {'bios': ['ro', 'a', 'b'], 'ec': ['ro', 'rw']}) |
| |
| if not errors: |
| logging.debug('versions correct: %s', after_fwids) |
| |
| if len(errors) == 1: |
| raise error.TestFail(errors[0]) |
| elif errors: |
| errors.insert(0, "%s: %s problems" % (case_desc, len(errors))) |
| raise error.TestFail('\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. |
| """ |
| if self.flashed: |
| if self.images_specified: |
| self.sync_and_ec_reboot('hard') |
| else: |
| logging.info("Restoring firmware") |
| self.restore_firmware() |
| |
| super(firmware_FWupdate, self).cleanup() |