| # Copyright 2019 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 |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| from autotest_lib.server.cros.faft.firmware_test import ConnectionError |
| |
| |
| class firmware_UpdaterModes(FirmwareTest): |
| """RO+RW firmware update using chromeos-firmwareupdate with various modes. |
| |
| This test uses --emulate, to avoid writing repeatedly to the flash. |
| """ |
| |
| version = 1 |
| |
| SHELLBALL = '/usr/sbin/chromeos-firmwareupdate' |
| |
| def initialize(self, host, cmdline_args, ec_wp=None): |
| """ |
| During initialization, back up the firmware, in case --emulate ever |
| breaks in a way that causes real writes. |
| """ |
| super(firmware_UpdaterModes, self).initialize(host, cmdline_args, ec_wp) |
| self.backup_firmware() |
| |
| def cleanup(self): |
| """Restore the original firmware, if it was somehow overwritten.""" |
| try: |
| if self.is_firmware_saved(): |
| self.restore_firmware() |
| except ConnectionError: |
| logging.error("ERROR: DUT did not come up after firmware restore!") |
| finally: |
| super(firmware_UpdaterModes, self).cleanup() |
| |
| def get_bios_fwids(self, path): |
| """Return the BIOS fwids for the given file""" |
| return self.faft_client.updater.get_image_fwids('bios', path) |
| |
| def run_case(self, mode, write_protected, written, modify_ro=True, |
| should_abort=False, writes_gbb=False): |
| """Run chromeos-firmwareupdate with given sub-case |
| |
| @param mode: factory or recovery or autoupdate |
| @param write_protected: is the flash write protected (--wp)? |
| @param modify_ro: should ro fwid be modified? |
| @param written: list of bios areas expected to change |
| @param should_abort: if True, the updater should abort with no changes |
| @param writes_gbb: if True, the updater should rewrite gbb flags. |
| @return: a list of failure messages for the case |
| """ |
| self.faft_client.updater.reset_shellball() |
| |
| fake_bios_path = self.faft_client.updater.copy_bios('fake-bios.bin') |
| self.faft_client.updater.set_image_gbb_flags(0, fake_bios_path) |
| |
| before_fwids = {'bios': self.get_bios_fwids(fake_bios_path)} |
| before_gbb = self.faft_client.updater.get_image_gbb_flags( |
| fake_bios_path) |
| |
| case_desc = ('chromeos-firmwareupdate --mode=%s --wp=%s' |
| % (mode, write_protected)) |
| |
| if modify_ro: |
| append = 'ro+rw' |
| else: |
| case_desc += ' [rw-only]' |
| append = 'rw' |
| |
| # Repack the shellball with modded fwids |
| self.modify_shellball(append, modify_ro) |
| modded_fwids = self.identify_shellball() |
| image_gbb = self.faft_client.updater.get_image_gbb_flags() |
| |
| options = ['--emulate', fake_bios_path, '--wp=%s' % write_protected] |
| |
| logging.info("%s (should write %s)", case_desc, |
| ', '.join(written).upper() or 'nothing') |
| |
| errors = [] |
| result = self.run_chromeos_firmwareupdate(mode, append, options, |
| ignore_status=True) |
| if result.exit_status == 0: |
| if should_abort: |
| errors.append( |
| "...updater: with current mode and write-protect value," |
| " should abort (rc!=0) and not modify anything") |
| else: |
| if should_abort: |
| logging.debug('updater aborted as expected') |
| else: |
| errors.append('...updater: unexpectedly failed (rc!=0)') |
| |
| after_fwids = {'bios': self.get_bios_fwids(fake_bios_path)} |
| after_gbb = self.faft_client.updater.get_image_gbb_flags(fake_bios_path) |
| expected_written = {'bios': written or []} |
| |
| errors += self.check_fwids_written( |
| before_fwids, modded_fwids, after_fwids, expected_written) |
| |
| if not errors: |
| logging.debug('...bios versions correct: %s', after_fwids['bios']) |
| |
| if writes_gbb: |
| if after_gbb != image_gbb: |
| # Expect rewritten, but it might not be different from before |
| errors.append( |
| "...GBB flags weren't rewritten to match the image: " |
| "before=0x%x, image=0x%x, after=0x%x." |
| % (before_gbb, image_gbb, after_gbb)) |
| else: |
| if after_gbb != before_gbb: |
| errors.append( |
| "...GBB flags were unexpectedly rewritten: " |
| "before=0x%x, image=0x%x, after=0x%x." |
| % (before_gbb, image_gbb, after_gbb)) |
| |
| if self.restore_firmware(): |
| # If real writes happen, fail immediately to avoid flash wear. |
| raise error.TestFail( |
| 'With chromeos-firmwareupdate --emulate, real flash device ' |
| 'was unexpectedly modified.') |
| |
| if errors: |
| case_message = '%s:\n%s' % (case_desc, '\n'.join(errors)) |
| logging.error('%s', case_message) |
| return [case_message] |
| return [] |
| |
| def run_once(self, host): |
| """Run test, iterating through combinations of mode and write-protect""" |
| errors = [] |
| |
| # TODO(dgoyette): Add a test that checks EC versions (can't be emulated) |
| |
| # factory: update A, B, and RO; reset gbb flags. If WP=1, abort. |
| errors += self.run_case('factory', 0, ['ro', 'a', 'b'], writes_gbb=True) |
| errors += self.run_case('factory', 1, [], should_abort=True) |
| |
| # recovery: update A and B, and RO if WP=0. |
| errors += self.run_case('recovery', 0, ['ro', 'a', 'b']) |
| errors += self.run_case('recovery', 1, ['a', 'b']) |
| |
| # autoupdate with changed ro: same as recovery (modify ro only if WP=0) |
| errors += self.run_case('autoupdate', 0, ['ro', 'a', 'b']) |
| errors += self.run_case('autoupdate', 1, ['b']) |
| |
| # autoupdate with unchanged ro: update inactive slot |
| errors += self.run_case('autoupdate', 0, ['b'], modify_ro=False) |
| errors += self.run_case('autoupdate', 1, ['b'], modify_ro=False) |
| |
| if len(errors) == 1: |
| raise error.TestFail(errors[0]) |
| elif errors: |
| raise error.TestFail( |
| '%s combinations of mode and write-protect failed:\n%s' % |
| (len(errors), '\n'.join(errors))) |