1# Copyright 2015 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 6import os 7 8from chromite.lib import remote_access 9from autotest_lib.client.common_lib import error 10from autotest_lib.client.common_lib import utils 11from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 12 13 14class firmware_FWupdate(FirmwareTest): 15 """RO+RW firmware update using chromeos-firmware with various modes. 16 If custom images are supplied, the DUT is left running that firmware, so the 17 test can be used to apply updates. Otherwise, it modifies the FWIDs of the 18 current firmware before flashing, and restores the firmware after the test. 19 20 Accepted --args names: 21 22 mode=[recovery|factory] 23 Run test with the given mode (default 'recovery') 24 25 new_bios= 26 new_ec= 27 new_pd= 28 apply the given image(s) instead of generating an update with fake fwids 29 30 """ 31 32 # Region to use for flashrom wp-region commands 33 WP_REGION = 'WP_RO' 34 35 def initialize(self, host, cmdline_args): 36 37 self.images_specified = False 38 self.flashed = False 39 40 dict_args = utils.args_to_dict(cmdline_args) 41 super(firmware_FWupdate, self).initialize(host, cmdline_args) 42 43 self.new_bios = dict_args.get('new_bios', None) 44 self.new_ec = dict_args.get('new_ec', None) 45 self.new_pd = dict_args.get('new_pd', None) 46 47 if self.new_bios: 48 self.images_specified = True 49 if not os.path.isfile(self.new_bios): 50 raise error.TestError('Specified BIOS file does not exist: %s' 51 % self.new_bios) 52 logging.info('new_bios=%s', self.new_bios) 53 54 if self.new_ec: 55 self.images_specified = True 56 if not os.path.isfile(self.new_ec): 57 raise error.TestError('Specified EC file does not exist: %s' 58 % self.new_ec) 59 logging.info('new_ec=%s', self.new_ec) 60 61 if self.new_pd: 62 self.images_specified = True 63 if not os.path.isfile(self.new_pd): 64 raise error.TestError('Specified PD file does not exist: %s' 65 % self.new_pd) 66 logging.info('new_pd=%s', self.new_pd) 67 68 self._old_bios_wp = self.faft_client.bios.get_write_protect_status() 69 70 if not self.images_specified: 71 # TODO(dgoyette): move this into the general FirmwareTest init? 72 stripped_bios = self.faft_client.bios.strip_modified_fwids() 73 if stripped_bios: 74 logging.warn( 75 "Fixed the previously modified BIOS FWID(s): %s", 76 stripped_bios) 77 78 if self.faft_config.chrome_ec: 79 stripped_ec = self.faft_client.ec.strip_modified_fwids() 80 if stripped_ec: 81 logging.warn( 82 "Fixed the previously modified EC FWID(s): %s", 83 stripped_ec) 84 85 self.backup_firmware() 86 87 if 'wp' in dict_args: 88 self.wp = int(dict_args['wp']) 89 else: 90 self.wp = None 91 92 self.set_hardware_write_protect(False) 93 self.faft_client.bios.set_write_protect_region(self.WP_REGION, True) 94 self.set_hardware_write_protect(True) 95 96 self.mode = dict_args.get('mode', 'recovery') 97 98 if self.mode not in ('factory', 'recovery'): 99 raise error.TestError('Unhandled mode: %s' % self.mode) 100 101 if self.mode == 'factory' and self.wp: 102 # firmware_UpdateModes already checks this case, so skip it here. 103 raise error.TestNAError( 104 "This test doesn't handle mode=factory with wp=1") 105 106 def get_installed_versions(self): 107 """Get the installed versions of BIOS and EC firmware. 108 109 @return: A nested dict keyed by target ('bios' or 'ec') and then section 110 @rtype: dict 111 """ 112 versions = dict() 113 versions['bios'] = self.faft_client.updater.get_all_installed_fwids( 114 'bios') 115 if self.faft_config.chrome_ec: 116 versions['ec'] = self.faft_client.updater.get_all_installed_fwids( 117 'ec') 118 return versions 119 120 def copy_cmdline_images(self, hostname): 121 """Copy the specified command line images into the extracted shellball. 122 123 @param hostname: hostname (not the Host object) to copy to 124 """ 125 if self.new_bios or self.new_ec or self.new_pd: 126 127 extract_dir = self.faft_client.updater.get_work_path() 128 129 dut_access = remote_access.RemoteDevice(hostname, username='root') 130 131 # Replace bin files. 132 if self.new_bios: 133 bios_rel = self.faft_client.updater.get_bios_relative_path() 134 bios_path = os.path.join(extract_dir, bios_rel) 135 dut_access.CopyToDevice(self.new_bios, bios_path, mode='scp') 136 137 if self.new_ec: 138 ec_rel = self.faft_client.updater.get_ec_relative_path() 139 ec_path = os.path.join(extract_dir, ec_rel) 140 dut_access.CopyToDevice(self.new_ec, ec_path, mode='scp') 141 142 if self.new_pd: 143 # note: pd.bin might likewise need special path logic 144 pd_path = os.path.join(extract_dir, 'pd.bin') 145 dut_access.CopyToDevice(self.new_pd, pd_path, mode='scp') 146 147 def run_case(self, append, write_protected, before_fwids, modded_fwids): 148 """Run chromeos-firmwareupdate with given sub-case 149 150 @param append: additional piece to add to shellball name 151 @param write_protected: is the flash write protected (--wp)? 152 @param before_fwids: fwids before flashing ('bios' and 'ec' as keys) 153 @param modded_fwids: fwids in image ('bios' and 'ec' as keys) 154 @return: a list of failure messages for the case 155 """ 156 157 cmd_desc = ('chromeos-firmwareupdate --mode=%s [wp=%s]' 158 % (self.mode, write_protected)) 159 160 # Unlock the protection of the wp-enable and wp-range registers 161 self.set_hardware_write_protect(False) 162 163 if write_protected: 164 self.faft_client.bios.set_write_protect_region(self.WP_REGION, True) 165 self.set_hardware_write_protect(True) 166 else: 167 self.faft_client.bios.set_write_protect_region( 168 self.WP_REGION, False) 169 170 expected_written = {} 171 written_desc = [] 172 173 if write_protected: 174 bios_written = ['a', 'b'] 175 ec_written = [] # EC write is all-or-nothing 176 177 else: 178 bios_written = ['ro', 'a', 'b'] 179 ec_written = ['ro', 'rw'] 180 181 expected_written['bios'] = bios_written 182 written_desc += ['bios %s' % '+'.join(bios_written)] 183 184 if self.faft_config.chrome_ec and ec_written: 185 expected_written['ec'] = ec_written 186 written_desc += ['ec %s' % '+'.join(ec_written)] 187 188 written_desc = '(should write %s)' % ', '.join(written_desc) 189 logging.info("Run %s %s", cmd_desc, written_desc) 190 191 # make sure we restore firmware after the test, if it tried to flash. 192 self.flashed = True 193 self.faft_client.updater.run_firmwareupdate(self.mode, append) 194 195 after_fwids = self.get_installed_versions() 196 197 errors = self.check_fwids_written( 198 before_fwids, modded_fwids, after_fwids, expected_written) 199 200 if errors: 201 logging.debug('%s', '\n'.join(errors)) 202 return ["%s: %s\n%s" % (cmd_desc, written_desc, '\n'.join(errors))] 203 else: 204 return [] 205 206 def run_once(self, host): 207 """Run chromeos-firmwareupdate with recovery or factory mode. 208 209 @param host: host to run on 210 """ 211 append = 'new' 212 have_ec = bool(self.faft_config.chrome_ec) 213 214 self.faft_client.updater.extract_shellball() 215 216 before_fwids = self.get_installed_versions() 217 218 # Repack shellball with modded fwids 219 if self.images_specified: 220 # Use new images as-is 221 logging.info( 222 "Using specified image(s):" 223 "new_bios=%s, new_ec=%s, new_pd=%s", 224 self.new_bios, self.new_ec, self.new_pd) 225 self.copy_cmdline_images(host.hostname) 226 self.faft_client.updater.reload_images() 227 self.faft_client.updater.repack_shellball(append) 228 modded_fwids = self.identify_shellball(include_ec=have_ec) 229 else: 230 # Modify the stock image 231 logging.info( 232 "Using the currently running firmware, with modified fwids") 233 self.setup_firmwareupdate_shellball() 234 self.faft_client.updater.reload_images() 235 self.modify_shellball(append, modify_ro=True, modify_ec=have_ec) 236 modded_fwids = self.identify_shellball(include_ec=have_ec) 237 238 fail_msg = "Section contents didn't show the expected changes." 239 240 errors = [] 241 if self.wp is not None: 242 # try only the specified wp= value 243 errors += self.run_case(append, self.wp, before_fwids, modded_fwids) 244 245 elif self.images_specified or self.mode == 'factory': 246 # apply images with wp=0 by default 247 errors += self.run_case(append, 0, before_fwids, modded_fwids) 248 249 else: 250 # no args specified, so check both wp=1 and wp=0 251 errors += self.run_case(append, 1, before_fwids, modded_fwids) 252 errors += self.run_case(append, 0, before_fwids, modded_fwids) 253 254 if errors: 255 raise error.TestFail("%s\n%s" % (fail_msg, '\n'.join(errors))) 256 257 def cleanup(self): 258 """ 259 If test was given custom images to apply, reboot the EC to apply them. 260 261 Otherwise, restore firmware from the backup taken before flashing. 262 No EC reboot is needed in that case, because the test didn't actually 263 reboot the EC with the new firmware. 264 """ 265 self.set_hardware_write_protect(False) 266 self.faft_client.bios.set_write_protect_range(0, 0, False) 267 268 if self.flashed: 269 if self.images_specified: 270 self.sync_and_ec_reboot('hard') 271 else: 272 logging.info("Restoring firmware") 273 self.restore_firmware() 274 275 # Restore the old write-protection value at the end of the test. 276 self.faft_client.bios.set_write_protect_range( 277 self._old_bios_wp['start'], 278 self._old_bios_wp['length'], 279 self._old_bios_wp['enabled']) 280 281 super(firmware_FWupdate, self).cleanup() 282