blob: d0b0b8319243e299051e539d6f297783f8481697 [file] [log] [blame]
# 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)))