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 # TODO(kmshelton): Move most of the logic in this test to a unit tested 30 # library. 31 def initialize(self, host): 32 self.host = host 33 # Make sure the client library is on the device so that the proxy 34 # code is there when we try to call it. 35 client_at = autotest.Autotest(self.host) 36 client_at.install() 37 self.faft_client = RPCProxy(self.host) 38 39 # Check if EC, PD is available. 40 # Check if DUT software write protect is disabled, failed otherwise. 41 self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled') 42 self.has_ec = False 43 mosys_output = self._run_cmd('mosys') 44 if 'EC information' in mosys_output: 45 self.has_ec = True 46 self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled') 47 48 def _run_cmd(self, command, checkfor=''): 49 """Run command on dut and return output. 50 Optionally check output contain string 'checkfor'. 51 """ 52 logging.info('Execute: %s', command) 53 output = self.host.run(command, ignore_status=True).stdout 54 logging.info('Output: %s', output.split('\n')) 55 if checkfor and checkfor not in ''.join(output): 56 raise error.TestFail('Expect %s in output of %s' % 57 (checkfor, ' '.join(output))) 58 return output 59 60 def _get_version(self): 61 """Retrive RO, RW EC/PD version.""" 62 ro = None 63 rw = None 64 lines = self._run_cmd('ectool version', checkfor='version') 65 for line in lines.splitlines(): 66 if line.startswith('RO version:'): 67 parts = line.split(':') 68 ro = parts[1].strip() 69 if line.startswith('RW version:'): 70 parts = line.split(':') 71 rw = parts[1].strip() 72 return (ro, rw) 73 74 def _bios_version(self): 75 """Retrive RO, RW BIOS version.""" 76 ro = self.faft_client.system.get_crossystem_value('ro_fwid') 77 rw = self.faft_client.system.get_crossystem_value('fwid') 78 return (ro, rw) 79 80 def _construct_fw_version(self, fw_ro, fw_rw): 81 """Construct a firmware version string in a consistent manner. 82 83 @param fw_ro: A string representing the version of a read-only 84 firmware. 85 @param fw_rw: A string representing the version of a read-write 86 firmware. 87 88 @returns a string constructed from fw_ro and fw_rw 89 90 """ 91 if fw_ro == fw_rw: 92 return fw_rw 93 else: 94 return '%s,%s' % (fw_ro, fw_rw) 95 96 def _get_version_all(self): 97 """Retrive BIOS, EC, and PD firmware version. 98 99 @return firmware version tuple (bios, ec) 100 """ 101 bios_version = None 102 ec_version = None 103 if self.has_ec: 104 (ec_ro, ec_rw) = self._get_version() 105 ec_version = self._construct_fw_version(ec_ro, ec_rw) 106 logging.info('Installed EC version: %s', ec_version) 107 (bios_ro, bios_rw) = self._bios_version() 108 bios_version = self._construct_fw_version(bios_ro, bios_rw) 109 logging.info('Installed BIOS version: %s', bios_version) 110 return (bios_version, ec_version) 111 112 def _get_shellball_version(self): 113 """Get shellball firmware version. 114 115 @return shellball firmware version tuple (bios, ec) 116 """ 117 bios = None 118 ec = None 119 bios_ro = None 120 bios_rw = None 121 ec_ro = None 122 ec_rw = None 123 shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V') 124 # TODO(kmshelton): Add a structured output option (likely a protobuf) 125 # to chromeos-firmwareupdate so the below can become less fragile. 126 for line in shellball.splitlines(): 127 if line.startswith('BIOS version:'): 128 parts = line.split(':') 129 bios_ro = parts[1].strip() 130 logging.info('shellball ro bios %s', bios_ro) 131 if line.startswith('BIOS (RW) version:'): 132 parts = line.split(':') 133 bios_rw = parts[1].strip() 134 logging.info('shellball rw bios %s', bios_rw) 135 if line.startswith('EC version:'): 136 parts = line.split(':') 137 ec_ro = parts[1].strip() 138 logging.info('shellball ro ec %s', ec_ro) 139 elif line.startswith('EC (RW) version:'): 140 parts = line.split(':') 141 ec_rw = parts[1].strip() 142 logging.info('shellball rw ec %s', ec_rw) 143 # Shellballs do not always contain a RW version. 144 if bios_rw is not None: 145 bios = self._construct_fw_version(bios_ro, bios_rw) 146 else: 147 bios = bios_ro 148 if ec_rw is not None: 149 ec = self._construct_fw_version(ec_ro, ec_rw) 150 else: 151 ec = ec_ro 152 return (bios, ec) 153 154 def run_once(self, replace=True): 155 # Get DUT installed firmware versions. 156 (installed_bios, installed_ec) = self._get_version_all() 157 158 # Get shellball firmware versions. 159 (shball_bios, shball_ec) = self._get_shellball_version() 160 161 # Figure out if update is needed. 162 need_update = False 163 if installed_bios != shball_bios: 164 need_update = True 165 logging.info('BIOS mismatch %s, will update to %s', 166 installed_bios, shball_bios) 167 if installed_ec and installed_ec != shball_ec: 168 need_update = True 169 logging.info('EC mismatch %s, will update to %s', 170 installed_ec, shball_ec) 171 172 # Update and reboot if needed. 173 if need_update: 174 output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate ' 175 ' --mode=recovery', 'SUCCESS') 176 self.host.reboot() 177 # Check that installed firmware match the shellball. 178 (bios, ec) = self._get_version_all() 179 # TODO(kmshelton): Refactor this test to use named tuples so that 180 # the comparison is eaiser to grok. 181 if (bios != shball_bios or ec != shball_ec): 182 logging.info('shball bios/ec: %s/%s', 183 shball_bios, shball_ec) 184 logging.info('installed bios/ec: %s/%s', bios, ec) 185 raise error.TestFail('Version mismatch after firmware update') 186 logging.info('*** Done firmware updated to match shellball. ***') 187 else: 188 logging.info('*** No firmware update is needed. ***') 189 190