# Copyright 2016 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 import autotest from autotest_lib.server import test from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy class platform_LabFirmwareUpdate(test.test): """For test or lab devices. Test will fail if Software write protection is enabled. Test will compare the installed firmware to those in the shellball. If differ, execute chromeos-firmwareupdate --mode=recovery to reset RO and RW firmware. Basic procedure are: - check software write protect, if enable, attemp reset. - fail test if software write protect is enabled. - check if ec is available on DUT. - get RO, RW versions of firmware, if RO != RW, update=True - get shellball versions of firmware - compare shellball version to DUT, update=True if shellball != DUT. - run chromeos-firwmareupdate --mode=recovery if update==True - reboot """ version = 1 # TODO(kmshelton): Move most of the logic in this test to a unit tested # library. def initialize(self, host): self.host = host # Make sure the client library is on the device so that the proxy # code is there when we try to call it. client_at = autotest.Autotest(self.host) client_at.install() self.faft_client = RPCProxy(self.host) # Check if EC, PD is available. # Check if DUT software write protect is disabled, failed otherwise. self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled') self.has_ec = False mosys_output = self._run_cmd('mosys') if 'EC information' in mosys_output: self.has_ec = True self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled') def _run_cmd(self, command, checkfor=''): """Run command on dut and return output. Optionally check output contain string 'checkfor'. """ logging.info('Execute: %s', command) output = self.host.run(command, ignore_status=True).stdout logging.info('Output: %s', output.split('\n')) if checkfor and checkfor not in ''.join(output): raise error.TestFail('Expect %s in output of %s' % (checkfor, ' '.join(output))) return output def _get_version(self): """Retrive RO, RW EC/PD version.""" ro = None rw = None lines = self._run_cmd('ectool version', checkfor='version') for line in lines.splitlines(): if line.startswith('RO version:'): parts = line.split(':') ro = parts[1].strip() if line.startswith('RW version:'): parts = line.split(':') rw = parts[1].strip() return (ro, rw) def _bios_version(self): """Retrive RO, RW BIOS version.""" ro = self.faft_client.system.get_crossystem_value('ro_fwid') rw = self.faft_client.system.get_crossystem_value('fwid') return (ro, rw) def _construct_fw_version(self, fw_ro, fw_rw): """Construct a firmware version string in a consistent manner. @param fw_ro: A string representing the version of a read-only firmware. @param fw_rw: A string representing the version of a read-write firmware. @returns a string constructed from fw_ro and fw_rw """ if fw_ro == fw_rw: return fw_rw else: return '%s,%s' % (fw_ro, fw_rw) def _get_version_all(self): """Retrive BIOS, EC, and PD firmware version. @return firmware version tuple (bios, ec) """ bios_version = None ec_version = None if self.has_ec: (ec_ro, ec_rw) = self._get_version() ec_version = self._construct_fw_version(ec_ro, ec_rw) logging.info('Installed EC version: %s', ec_version) (bios_ro, bios_rw) = self._bios_version() bios_version = self._construct_fw_version(bios_ro, bios_rw) logging.info('Installed BIOS version: %s', bios_version) return (bios_version, ec_version) def _get_shellball_version(self): """Get shellball firmware version. @return shellball firmware version tuple (bios, ec) """ bios = None ec = None bios_ro = None bios_rw = None ec_ro = None ec_rw = None shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V') # TODO(kmshelton): Add a structured output option (likely a protobuf) # to chromeos-firmwareupdate so the below can become less fragile. for line in shellball.splitlines(): if line.startswith('BIOS version:'): parts = line.split(':') bios_ro = parts[1].strip() logging.info('shellball ro bios %s', bios_ro) if line.startswith('BIOS (RW) version:'): parts = line.split(':') bios_rw = parts[1].strip() logging.info('shellball rw bios %s', bios_rw) if line.startswith('EC version:'): parts = line.split(':') ec_ro = parts[1].strip() logging.info('shellball ro ec %s', ec_ro) elif line.startswith('EC (RW) version:'): parts = line.split(':') ec_rw = parts[1].strip() logging.info('shellball rw ec %s', ec_rw) # Shellballs do not always contain a RW version. if bios_rw is not None: bios = self._construct_fw_version(bios_ro, bios_rw) else: bios = bios_ro if ec_rw is not None: ec = self._construct_fw_version(ec_ro, ec_rw) else: ec = ec_ro return (bios, ec) def run_once(self, replace=True): # Get DUT installed firmware versions. (installed_bios, installed_ec) = self._get_version_all() # Get shellball firmware versions. (shball_bios, shball_ec) = self._get_shellball_version() # Figure out if update is needed. need_update = False if installed_bios != shball_bios: need_update = True logging.info('BIOS mismatch %s, will update to %s', installed_bios, shball_bios) if installed_ec and installed_ec != shball_ec: need_update = True logging.info('EC mismatch %s, will update to %s', installed_ec, shball_ec) # Update and reboot if needed. if need_update: output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate ' ' --mode=recovery', '(recovery) completed.') self.host.reboot() # Check that installed firmware match the shellball. (bios, ec) = self._get_version_all() # TODO(kmshelton): Refactor this test to use named tuples so that # the comparison is eaiser to grok. if (bios != shball_bios or ec != shball_ec): logging.info('shball bios/ec: %s/%s', shball_bios, shball_ec) logging.info('installed bios/ec: %s/%s', bios, ec) raise error.TestFail('Version mismatch after firmware update') logging.info('*** Done firmware updated to match shellball. ***') else: logging.info('*** No firmware update is needed. ***')