blob: 4a9f31f39359d6c24a8c0da9631657f989dd7f09 [file] [log] [blame]
# 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.get_write_protect_status()
if not self.images_specified:
# TODO(dgoyette): move this into the general FirmwareTest init?
stripped_bios = self.faft_client.bios.strip_modified_fwids()
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.strip_modified_fwids()
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.set_write_protect_region(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.get_all_installed_fwids(
'bios')
if self.faft_config.chrome_ec:
versions['ec'] = self.faft_client.updater.get_all_installed_fwids(
'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.get_work_path()
dut_access = remote_access.RemoteDevice(hostname, username='root')
# Replace bin files.
if self.new_bios:
bios_rel = self.faft_client.updater.get_bios_relative_path()
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.get_ec_relative_path()
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.set_write_protect_region(self.WP_REGION, True)
self.set_hardware_write_protect(True)
else:
self.faft_client.bios.set_write_protect_region(
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.run_firmwareupdate(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.extract_shellball()
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.reload_images()
self.faft_client.updater.repack_shellball(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.reload_images()
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.set_write_protect_range(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.set_write_protect_range(
self._old_bios_wp['start'],
self._old_bios_wp['length'],
self._old_bios_wp['enabled'])
super(firmware_FWupdate, self).cleanup()