# Copyright 2020 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 xmlrpclib from autotest_lib.client.common_lib import error from autotest_lib.server.cros.faft.firmware_test import FirmwareTest from autotest_lib.server.cros.power import utils as PowerUtils class firmware_FWupdateThenSleep(FirmwareTest): """Firmware update using chromeos-firmwareupdate --mode=recovery, then sleep and wake, then make sure the host responds and the flash contents don't change. """ MODE = 'recovery' MIN_BATTERY_LEVEL = 20 def initialize(self, host, cmdline_args, battery_only=False): self.flashed = False self._want_restore = None self._original_sw_wp = {} self._original_hw_wp = None super(firmware_FWupdateThenSleep, self).initialize(host, cmdline_args) ac_online = self._client.is_ac_connected() battery_percentage = self._client.get_battery_display_percentage() self.have_power_control = False if battery_only: if not self._client.has_battery(): raise error.TestNAError("DUT type does not have a battery.") if self.servo.supports_built_in_pd_control(): self.have_power_control = True PowerUtils.put_host_battery_in_range(self._client, min_level=25, max_level=100, timeout=600) elif ac_online: raise error.TestNAError( "For this version of the test, the DUT power supply must" " be unplugged, or connected through Servo V4 Type-C.") elif battery_percentage < self.MIN_BATTERY_LEVEL: raise error.TestError( "Battery level is too low (%s%%). Please charge the DUT " "to at least %s%%, then try again." % (battery_percentage, self.MIN_BATTERY_LEVEL)) else: logging.info("On battery: %s%% charge", battery_percentage) self._original_sw_wp = self.faft_client.bios.get_write_protect_status() self._original_hw_wp = 'on' in self.servo.get('fw_wp_state') self.backup_firmware() self.setup_firmwareupdate_shellball() if self.faft_config.ap_access_ec_flash: self._setup_ec_write_protect(False) self.set_ap_write_protect_and_reboot(False) self.faft_client.bios.set_write_protect_range(0, 0, False) if battery_only and self.have_power_control: self.set_servo_v4_role_to_snk() def cleanup(self): """Restore the original firmware and original write-protect.""" self._restore_servo_v4_role() self.set_ap_write_protect_and_reboot(False) try: if self.flashed and self.is_firmware_saved(): self.restore_firmware() except (EnvironmentError, xmlrpclib.Fault, error.AutoservError, error.TestBaseException): logging.error("Problem restoring firmware:", exc_info=True) try: # Restore the old write-protection value at the end of the test. if self._original_sw_wp: self.faft_client.bios.set_write_protect_range( self._original_sw_wp['start'], self._original_sw_wp['length'], self._original_sw_wp['enabled']) except (EnvironmentError, xmlrpclib.Fault, error.AutoservError, error.TestBaseException): logging.error("Problem restoring SW write-protect:", exc_info=True) if self._original_hw_wp is not None: self.set_ap_write_protect_and_reboot(self._original_hw_wp) super(firmware_FWupdateThenSleep, self).cleanup() 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_device_fwids('bios') if self.faft_config.chrome_ec: versions['ec'] = self.faft_client.updater.get_device_fwids('ec') return versions def apply_update(self, append, before_fwids, image_fwids, wp=0): """Run chromeos-firmwareupdate with given sub-case @param append: additional piece to add to shellball name @param wp: is the flash write protected (--wp)? @return: a list of failure messages for the case """ options = ['--wp=%s' % wp, '--quirks=ec_partial_recovery=0'] if append: cmd_desc = ['chromeos-firmwareupdate-%s' % append] else: cmd_desc = ['chromeos-firmwareupdate'] cmd_desc += ['--mode=%s' % self.MODE] cmd_desc += options cmd_desc = ' '.join(cmd_desc) expected_written = {} if wp: 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 if self.faft_config.chrome_ec and ec_written: expected_written['ec'] = ec_written # remove quotes and braces: bios: [a, b], ec: [ro, rw] written_desc = repr(expected_written).replace("'", "")[1:-1] logging.debug('Before(%s): %s', append or '', before_fwids) logging.debug('Image(%s): %s', append or '', image_fwids) logging.info("Run %s (should write %s)", cmd_desc, written_desc) # make sure we restore firmware after the test, if it tried to flash. self.flashed = True errors = [] result = self.run_chromeos_firmwareupdate( self.MODE, append, options, ignore_status=True) if result.exit_status == 255: logging.warn("DUT network dropped during update.") elif result.exit_status != 0: if (image_fwids == before_fwids and 'Good. It seems nothing was changed.' in result.stdout): logging.info("DUT already matched the image; updater aborted.") else: errors.append('...updater: unexpectedly failed (rc=%s)' % result.exit_status) after_fwids = self.get_installed_versions() logging.debug('After(%s): %s', append or '', after_fwids) errors += self.check_fwids_written( before_fwids, image_fwids, after_fwids, expected_written) if errors: logging.debug('%s', '\n'.join(errors)) return ["%s (should write %s)\n%s" % (cmd_desc, written_desc, '\n'.join(errors))] return [] def run_once(self, wp=0, battery_only=False): """Try a firmware update, then put the machine in sleep and wake it.""" errors = [] original_boot_id = self._client.get_boot_id() have_ec = bool(self.faft_config.chrome_ec) before_fwids = self.get_installed_versions() self.modify_shellball('mod', modify_ro=True, modify_ec=have_ec) modded_fwids = self.identify_shellball(include_ec=have_ec) # Check power again, in case it got reconnected during setup. if battery_only and self._client.is_ac_connected(): if self.have_power_control: raise error.TestError( "DUT is on AC power, even though Servo role was set to" " sink. Is a second power supply connected?") else: raise error.TestError( "DUT is back on AC power, when it should be on battery." " Did it get plugged back in?") errors += self.apply_update('mod', before_fwids, modded_fwids, wp=wp) if errors: fail_msg = "Problem(s) occurred during flash, before sleep:" errors.insert(0, fail_msg) raise error.TestFail('\n'.join(errors)) logging.info("Firmware flashed successfully; trying sleep.") before_sleep_fwids = self.get_installed_versions() self.suspend() if self.faft_config.chrome_ec: if not self.wait_power_state( self.POWER_STATE_SUSPEND, self.DEFAULT_PWR_RETRIES): raise error.TestFail('Platform failed to reach a suspend state' ' after flashing firmware.') else: self.switcher.wait_for_client_offline(orig_boot_id=original_boot_id) logging.info("System should now be in sleep; trying to wake.") self.servo.power_normal_press() self.switcher.wait_for_client() after_sleep_fwids = self.get_installed_versions() logging.debug('After sleep+wake: %s', after_sleep_fwids) if have_ec: expected_changes = {'bios': None, 'ec': None} else: expected_changes = {'bios': None} after_sleep_errors = self.check_fwids_written( before_sleep_fwids, None, after_sleep_fwids, expected_changes) if after_sleep_errors: errors += ['After sleep, FWIDs unexpectedly differ from' ' what was written:'] + after_sleep_errors if errors: fail_msg = "Problem(s) occurred during flash + sleep + wake:" errors.insert(0, fail_msg) raise error.TestFail('\n'.join(errors))