blob: b1c735fddbdad45eea340db1ed2d963f7f681c58 [file] [log] [blame]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The autotest performing FW update, both EC and AP in CCD mode."""
import logging
import os
from autotest_lib.client.common_lib import error
from autotest_lib.server.cros.faft.cr50_test import Cr50Test
from autotest_lib.server.cros.servo import servo
MAX_EC_CLEANUP_TRIES=2
MAX_EC_RESPONSE_TRIES=3
class firmware_Cr50CCDFirmwareUpdate(Cr50Test):
"""A test that can provision a machine to the correct firmware version."""
version = 1
should_restore_fw = False
BIOS_NAME = 'ccd.image.bin'
EC_NAME = 'ccd.ec.bin'
def initialize(self, host, cmdline_args, full_args, fw_path=None):
"""Initialize the test and check if cr50 exists.
Raises:
TestNAError: If the dut is not proper for this test for its RDD
recognition problem.
"""
if not host.servo:
raise error.TestNAError('Unable to start servod')
servo_type = host.servo.get_servo_version()
if 'ccd' not in servo_type:
raise error.TestNAError('unsupported servo type: %s' % servo_type)
super(firmware_Cr50CCDFirmwareUpdate,
self).initialize(host, cmdline_args, full_args)
# Don't bother if there is no Chrome EC.
if not self.check_ec_capability():
raise error.TestNAError('Nothing needs to be tested on this device')
self.install_ec = full_args.get('skip_ec_fw_update',
'').lower() != 'true'
self.fw_path = fw_path
self.fw_build = full_args.get('fw_build', '')
self.ec_fw_path = full_args.get('ec_fw_path', None)
self.bios_fw_path = full_args.get('bios_fw_path', None)
if eval(full_args.get('backup_fw', 'False')):
self.backup_firmware()
def cleanup(self):
try:
if not self.should_restore_fw:
return
self.servo.enable_main_servo_device()
self.gsc.reboot()
# Verify the EC is responsive before raising an error and going to
# cleanup. Repair and cleanup don't recover corrupted EC firmware
# very well.
try:
self.verify_ec_response()
except Exception as e:
logging.error('Caught exception: %s', str(e))
if self.is_firmware_saved():
logging.info('Restoring firmware')
self.restore_firmware()
else:
logging.info('chromeos-firmwareupdate --mode=recovery')
result = self._client.run('chromeos-firmwareupdate'
' --mode=recovery',
ignore_status=True,
timeout=600)
if result.exit_status != 0:
logging.error('chromeos-firmwareupdate failed: %s',
result.stdout.strip())
self._client.reboot()
except Exception as e:
logging.error('Caught exception: %s', str(e))
finally:
super(firmware_Cr50CCDFirmwareUpdate, self).cleanup()
def verify_ec_response(self):
""" Verify the EC is responsive."""
if not self.install_ec:
return
# Try to reflash EC a couple of times to see if it's possible to recover
# the device now.
for _ in range(MAX_EC_CLEANUP_TRIES):
# Try a few times to get response before resorting to reflash.
for _ in range(MAX_EC_RESPONSE_TRIES):
try:
if self.servo.get_ec_board():
return
except servo.ConsoleError as e:
logging.error('EC console is unresponsive: %s', str(e))
try:
self.cros_host.firmware_install(build=self.fw_build,
local_tarball=self.fw_path,
install_bios=False,
ec_image=self.ec_fw_path)
except Exception as e:
logging.error('firmware_install failed: %s', str(e))
logging.error('DUT likely needs a manual recovery.')
def _download_firmware_from_host(self):
"""Download the ap and ec image from the dut.
Save the ec and ap firmware image locally, so the test can use them to
verify flashing the ec and ap with ccd.
"""
if self.ec_fw_path and self.bios_fw_path:
logging.info('User supplied ec and ap fw images')
return
self.setup_firmwareupdate_shellball()
work_path = self.faft_client.updater.get_work_path()
if not self.bios_fw_path:
self.bios_fw_path = os.path.join(self.resultsdir, self.BIOS_NAME)
bios_name = self.faft_client.updater.get_bios_relative_path()
bios_path = os.path.join(work_path, bios_name)
logging.info('Using DUT bios image %s', bios_path)
self._client.get_file(bios_path, self.bios_fw_path)
if not self.ec_fw_path:
self.ec_fw_path = os.path.join(self.resultsdir, self.EC_NAME)
ec_name = self.faft_client.updater.get_ec_relative_path()
ec_path = os.path.join(work_path, ec_name)
logging.info('Using DUT ec image %s', ec_path)
self._client.get_file(ec_path, self.ec_fw_path)
def run_once(self, host, rw_only=False):
"""The method called by the control file to start the test.
Args:
host: a CrosHost object of the machine to update.
rw_only: True to only update the RW firmware.
Raises:
TestFail: if the firmware version remains unchanged.
TestError: if the latest firmware release cannot be located.
TestNAError: if the test environment is not properly set.
e.g. the servo type doesn't support this test.
"""
self.cros_host = host
board = (getattr(self.faft_config, 'parent', None)
or getattr(self.faft_config, 'platform', None))
platform = self.cros_host.get_platform()
# Allow faft_config.fw_update_build to override
# get_latest_release_version().
config_fw_build = getattr(self.faft_config, 'fw_update_build', None)
if self.fw_build:
logging.info('using fw build: %s', self.fw_build)
elif self.fw_path:
logging.info('using fw tarball: %s', self.fw_path)
else:
self._download_firmware_from_host()
# Find the latest firmware build, so the test can download
# npcx_monitor.bin.
if 'npcx' in self.servo.get('ec_chip'):
try:
self.fw_build = self.cros_host.get_latest_release_version(
platform, board)
except:
self.fw_build = None
logging.info('using EC image: %s', self.ec_fw_path)
logging.info('using bios image: %s', self.bios_fw_path)
# Fast open cr50 and check if testlab is enabled.
self.fast_ccd_open(enable_testlab=True)
if not self.servo.enable_ccd_servo_device():
raise error.TestNAError('Cannot make ccd active')
# Add support for downloading npcx_monitor.bin without a tarball.
if not self.fw_path and self.servo.get('ec_chip').startswith('npcx'):
self.install_ec = False
# If it is ITE EC, then allow CCD I2C access for flashing EC.
if self.servo.get('ec_chip').startswith('it8'):
# TODO(b/302193501): enable test on ite devices once the labstation
# supports it.
logging.info(
'Skip flash_ec. i2c-pseudo not supported on labstation')
self.install_ec = False
#self.gsc.set_cap('I2C', 'Always')
# Make sure to use the GSC ec_reset command for cold reset snce that's
# what normal ccd devices will use.
if self.servo.has_control('cold_reset_select'):
self.servo.set('cold_reset_select', 'gsc_ec_reset')
# TODO(b/196824029): remove when servod supports using the power state
# controller with the ccd device.
try:
self.host.servo.get_power_state_controller().reset()
except Exception as e:
logging.info(e)
raise error.TestNAError('Unable to do power state reset with '
'active ccd device')
# Flashing the dut involves running power_state:reset. If this locks
# ccd, flashing won't work. Raise an error to fix cold_reset.
if self.gsc.get_ccd_level() != self.gsc.OPEN:
raise error.TestError(
'Resetting the dut locked ccd. Flashing with '
'CCD will not work. Switch cold_reset to '
'gsc_ec_reset')
self.should_restore_fw = True
if not self.install_ec:
self.ec_fw_path = None
try:
self.cros_host.firmware_install(build=self.fw_build,
rw_only=rw_only,
local_tarball=self.fw_path,
dest=self.resultsdir,
verify_version=True,
install_ec=self.install_ec,
ec_image=self.ec_fw_path,
bios_image=self.bios_fw_path)
except Exception as e:
# The test failed to flash the firmware.
raise error.TestFail('firmware_install failed with CCD: %s' %
str(e))