1# Copyright 2016 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6 7from autotest_lib.client.common_lib import error 8from autotest_lib.server import autotest 9from autotest_lib.server import test 10from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy 11 12class platform_LabFirmwareUpdate(test.test): 13 """For test or lab devices. Test will fail if Software write protection 14 is enabled. Test will compare the installed firmware to those in 15 the shellball. If differ, execute chromeos-firmwareupdate 16 --mode=recovery to reset RO and RW firmware. Basic procedure are: 17 18 - check software write protect, if enable, attemp reset. 19 - fail test if software write protect is enabled. 20 - check if ec is available on DUT. 21 - get RO, RW versions of firmware, if RO != RW, update=True 22 - get shellball versions of firmware 23 - compare shellball version to DUT, update=True if shellball != DUT. 24 - run chromeos-firwmareupdate --mode=recovery if update==True 25 - reboot 26 """ 27 version = 1 28 29 def initialize(self, host): 30 self.host = host 31 # Make sure the client library is on the device so that the proxy 32 # code is there when we try to call it. 33 client_at = autotest.Autotest(self.host) 34 client_at.install() 35 self.faft_client = RPCProxy(self.host) 36 37 # Check if EC, PD is available. 38 # Check if DUT software write protect is disabled, failed otherwise. 39 self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled') 40 self.has_ec = False 41 mosys_output = self._run_cmd('mosys') 42 if 'EC information' in mosys_output: 43 self.has_ec = True 44 self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled') 45 46 def _run_cmd(self, command, checkfor=''): 47 """Run command on dut and return output. 48 Optionally check output contain string 'checkfor'. 49 """ 50 logging.info('Execute: %s', command) 51 output = self.host.run(command, ignore_status=True).stdout 52 logging.info('Output: %s', output.split('\n')) 53 if checkfor and checkfor not in ''.join(output): 54 raise error.TestFail('Expect %s in output of %s' % 55 (checkfor, ' '.join(output))) 56 return output 57 58 def _get_version(self): 59 """Retrive RO, RW EC/PD version.""" 60 ro = None 61 rw = None 62 lines = self._run_cmd('ectool version', checkfor='version') 63 for line in lines.splitlines(): 64 if line.startswith('RO version:'): 65 parts = line.split(':') 66 ro = parts[1].strip() 67 if line.startswith('RW version:'): 68 parts = line.split(':') 69 rw = parts[1].strip() 70 return (ro, rw) 71 72 def _bios_version(self): 73 """Retrive RO, RW BIOS version.""" 74 ro = self.faft_client.system.get_crossystem_value('ro_fwid') 75 rw = self.faft_client.system.get_crossystem_value('fwid') 76 return (ro, rw) 77 78 def _construct_fw_version(self, fw_ro, fw_rw): 79 """Construct a firmware version string in a consistent manner. 80 81 @param fw_ro: A string representing the version of a read-only 82 firmware. 83 @param fw_rw: A string representing the version of a read-write 84 firmware. 85 86 @returns a string constructed from fw_ro and fw_rw 87 88 """ 89 if fw_ro == fw_rw: 90 return fw_rw 91 else: 92 return '%s,%s' % (fw_ro, fw_rw) 93 94 def _get_version_all(self): 95 """Retrive BIOS, EC, and PD firmware version. 96 97 @return firmware version tuple (bios, ec) 98 """ 99 bios_version = None 100 ec_version = None 101 if self.has_ec: 102 (ec_ro, ec_rw) = self._get_version() 103 ec_version = self._construct_fw_version(ec_ro, ec_rw) 104 logging.info('Installed EC version: %s', ec_version) 105 (bios_ro, bios_rw) = self._bios_version() 106 bios_version = self._construct_fw_version(bios_ro, bios_rw) 107 logging.info('Installed BIOS version: %s', bios_version) 108 return (bios_version, ec_version) 109 110 def _get_shellball_version(self): 111 """Get shellball firmware version. 112 113 @return shellball firmware version tuple (bios, ec) 114 """ 115 ec = None 116 bios = None 117 bios_ro = None 118 bios_rw = None 119 shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V') 120 for line in shellball.splitlines(): 121 if line.startswith('BIOS version:'): 122 parts = line.split(':') 123 bios_ro = parts[1].strip() 124 logging.info('shellball ro bios %s', bios_ro) 125 if line.startswith('BIOS (RW) version:'): 126 parts = line.split(':') 127 bios_rw = parts[1].strip() 128 logging.info('shellball rw bios %s', bios_rw) 129 elif line.startswith('EC version:'): 130 parts = line.split(':') 131 ec = parts[1].strip() 132 logging.info('shellball ec %s', ec) 133 # Shellballs do not always contain a RW version. 134 if bios_rw is not None: 135 bios = self._construct_fw_version(bios_ro, bios_rw) 136 else: 137 bios = bios_ro 138 return (bios, ec) 139 140 def run_once(self, replace=True): 141 # Get DUT installed firmware versions. 142 (installed_bios, installed_ec) = self._get_version_all() 143 144 # Get shellball firmware versions. 145 (shball_bios, shball_ec) = self._get_shellball_version() 146 147 # Figure out if update is needed. 148 need_update = False 149 if installed_bios != shball_bios: 150 need_update = True 151 logging.info('BIOS mismatch %s, will update to %s', 152 installed_bios, shball_bios) 153 if installed_ec and installed_ec != shball_ec: 154 need_update = True 155 logging.info('EC mismatch %s, will update to %s', 156 installed_ec, shball_ec) 157 158 # Update and reboot if needed. 159 if need_update: 160 output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate ' 161 ' --mode=recovery', '(recovery) completed.') 162 self.host.reboot() 163 # Check that installed firmware match the shellball. 164 (bios, ec) = self._get_version_all() 165 # TODO(kmshelton): Refactor this test to use named tuples so that 166 # the comparison is eaiser to grok. 167 if (bios != shball_bios or ec != shball_ec): 168 logging.info('shball bios/ec: %s/%s', 169 shball_bios, shball_ec) 170 logging.info('installed bios/ec: %s/%s', bios, ec) 171 raise error.TestFail('Version mismatch after firmware update') 172 logging.info('*** Done firmware updated to match shellball. ***') 173 else: 174 logging.info('*** No firmware update is needed. ***') 175 176