1# Copyright (c) 2012 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"""A module to support automatic firmware update. 5 6See FirmwareUpdater object below. 7""" 8import array 9import json 10import os 11 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.common_lib.cros import chip_utils 14from autotest_lib.client.common_lib.cros import cros_config 15from autotest_lib.client.cros.faft.utils import flashrom_handler 16 17 18class FirmwareUpdaterError(Exception): 19 """Error in the FirmwareUpdater module.""" 20 21 22class FirmwareUpdater(object): 23 """An object to support firmware update. 24 25 This object will create a temporary directory in /usr/local/tmp/faft/autest 26 with two subdirs, keys/ and work/. You can modify the keys in keys/ dir. If 27 you want to provide a given shellball to do firmware update, put shellball 28 under /usr/local/tmp/faft/autest with name chromeos-firmwareupdate. 29 30 @type os_if: autotest_lib.client.cros.faft.utils.os_interface.OSInterface 31 """ 32 33 DAEMON = 'update-engine' 34 CBFSTOOL = 'cbfstool' 35 HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\'' 36 37 DEFAULT_SHELLBALL = '/usr/sbin/chromeos-firmwareupdate' 38 DEFAULT_SUBDIR = 'autest' # subdirectory of os_interface.state_dir 39 DEFAULT_SECTION_FOR_TARGET = {'bios': 'a', 'ec': 'rw'} 40 41 def __init__(self, os_if): 42 """Initialize the updater tools, but don't load the image data yet.""" 43 self.os_if = os_if 44 self._temp_path = self.os_if.state_dir_file(self.DEFAULT_SUBDIR) 45 self._cbfs_work_path = os.path.join(self._temp_path, 'cbfs') 46 self._keys_path = os.path.join(self._temp_path, 'keys') 47 self._work_path = os.path.join(self._temp_path, 'work') 48 self._bios_path = 'bios.bin' 49 self._ec_path = 'ec.bin' 50 51 self.pubkey_path = os.path.join(self._keys_path, 'root_key.vbpubk') 52 self._real_bios_handler = self._create_handler('bios') 53 self._real_ec_handler = self._create_handler('ec') 54 self.initialized = False 55 56 def init(self): 57 """Extract the shellball and other files, unless they already exist.""" 58 59 if self.os_if.is_dir(self._work_path): 60 # If work dir is present, assume the whole temp dir is usable as-is. 61 self._detect_image_paths() 62 else: 63 # If work dir is missing, assume the whole temp dir is unusable, and 64 # recreate it. 65 self._create_temp_dir() 66 self.extract_shellball() 67 68 self.initialized = True 69 70 def _get_handler(self, target): 71 """Return the handler for the target, after initializing it if needed. 72 73 @param target: image type ('bios' or 'ec') 74 @return: the handler for that target 75 76 @type target: str 77 @rtype: flashrom_handler.FlashromHandler 78 """ 79 if target == 'bios': 80 if not self._real_bios_handler.initialized: 81 bios_file = self._get_image_path('bios') 82 self._real_bios_handler.init(bios_file) 83 return self._real_bios_handler 84 elif target == 'ec': 85 if not self._real_ec_handler.initialized: 86 ec_file = self._get_image_path('ec') 87 self._real_ec_handler.init(ec_file, allow_fallback=True) 88 return self._real_ec_handler 89 else: 90 raise FirmwareUpdaterError("Unhandled target: %r" % target) 91 92 def _create_handler(self, target, suffix=None): 93 """Return a new (not pre-populated) handler for the given target, 94 such as for use in checking installed versions. 95 96 @param target: image type ('bios' or 'ec') 97 @param suffix: additional piece for subdirectory of handler 98 Example: 'tmp' -> 'autest/<target>.tmp/' 99 @return: a new handler for that target 100 101 @type target: str 102 @rtype: flashrom_handler.FlashromHandler 103 """ 104 if suffix: 105 subdir = '%s/%s.%s' % (self.DEFAULT_SUBDIR, target, suffix) 106 else: 107 subdir = '%s/%s' % (self.DEFAULT_SUBDIR, target) 108 return flashrom_handler.FlashromHandler( 109 self.os_if, self.pubkey_path, self._keys_path, target=target, 110 subdir=subdir) 111 112 def _get_image_path(self, target): 113 """Return the handler for the given target 114 115 @param target: image type ('bios' or 'ec') 116 @return: the path of the image file for that target 117 118 @type target: str 119 @rtype: str 120 """ 121 if target == 'bios': 122 return os.path.join(self._work_path, self._bios_path) 123 elif target == 'ec': 124 return os.path.join(self._work_path, self._ec_path) 125 else: 126 raise FirmwareUpdaterError("Unhandled target: %r" % target) 127 128 def _get_default_section(self, target): 129 """Return the default section to work with, for the given target 130 131 @param target: image type ('bios' or 'ec') 132 @return: the default section for that target 133 134 @type target: str 135 @rtype: str 136 """ 137 if target in self.DEFAULT_SECTION_FOR_TARGET: 138 return self.DEFAULT_SECTION_FOR_TARGET[target] 139 else: 140 raise FirmwareUpdaterError("Unhandled target: %r" % target) 141 142 def _create_temp_dir(self): 143 """Create (or recreate) the temporary directory. 144 145 The default /usr/sbin/chromeos-firmwareupdate is copied into _temp_dir, 146 and devkeys are copied to _key_path. The caller is responsible for 147 extracting the copied shellball. 148 """ 149 self.cleanup_temp_dir() 150 151 self.os_if.create_dir(self._temp_path) 152 self.os_if.create_dir(self._cbfs_work_path) 153 self.os_if.create_dir(self._work_path) 154 self.os_if.copy_dir('/usr/share/vboot/devkeys', self._keys_path) 155 156 working_shellball = os.path.join(self._temp_path, 157 'chromeos-firmwareupdate') 158 self.os_if.copy_file(self.DEFAULT_SHELLBALL, working_shellball) 159 160 def cleanup_temp_dir(self): 161 """Cleanup temporary directory.""" 162 if self.os_if.is_dir(self._temp_path): 163 self.os_if.remove_dir(self._temp_path) 164 165 def stop_daemon(self): 166 """Stop update-engine daemon.""" 167 self.os_if.log('Stopping %s...' % self.DAEMON) 168 cmd = 'status %s | grep stop || stop %s' % (self.DAEMON, self.DAEMON) 169 self.os_if.run_shell_command(cmd) 170 171 def start_daemon(self): 172 """Start update-engine daemon.""" 173 self.os_if.log('Starting %s...' % self.DAEMON) 174 cmd = 'status %s | grep start || start %s' % (self.DAEMON, self.DAEMON) 175 self.os_if.run_shell_command(cmd) 176 177 def get_ec_hash(self): 178 """Retrieve the hex string of the EC hash.""" 179 ec = self._get_handler('ec') 180 return ec.get_section_hash('rw') 181 182 def get_section_fwid(self, target='bios', section=None): 183 """Get one fwid from in-memory image, for the given target. 184 185 @param target: the image type to get from: 'bios (default) or 'ec' 186 @param section: section to return. Default: A for bios, RW for EC 187 188 @type target: str | None 189 @rtype: str 190 """ 191 if section is None: 192 section = self._get_default_section(target) 193 image_path = self._get_image_path(target) 194 if target == 'ec' and not os.path.isfile(image_path): 195 # If the EC image is missing, report a specific error message. 196 raise FirmwareUpdaterError("Shellball does not contain ec.bin") 197 198 handler = self._get_handler(target) 199 handler.new_image(image_path) 200 fwid = handler.get_section_fwid(section) 201 if fwid is not None: 202 return str(fwid) 203 else: 204 return None 205 206 def get_all_fwids(self, target='bios'): 207 """Get all non-empty fwids from in-memory image, for the given target. 208 209 @param target: the image type to get from: 'bios' (default) or 'ec' 210 @return: fwid for the sections 211 212 @type target: str 213 @rtype: dict | None 214 """ 215 image_path = self._get_image_path(target) 216 if target == 'ec' and not os.path.isfile(image_path): 217 # If the EC image is missing, report a specific error message. 218 raise FirmwareUpdaterError("Shellball does not contain ec.bin") 219 220 handler = self._get_handler(target) 221 handler.new_image(image_path) 222 223 fwids = {} 224 for section in handler.fv_sections: 225 fwid = handler.get_section_fwid(section) 226 if fwid is not None: 227 fwids[section] = fwid 228 return fwids 229 230 def get_all_installed_fwids(self, target='bios', filename=None): 231 """Get all non-empty fwids from disk or flash, for the given target. 232 233 @param target: the image type to get from: 'bios' (default) or 'ec' 234 @param filename: filename to read instead of using the actual flash 235 @return: fwid for the sections 236 237 @type target: str 238 @type filename: str 239 @rtype: dict 240 """ 241 handler = self._create_handler(target, 'installed') 242 if filename: 243 filename = os.path.join(self._temp_path, filename) 244 handler.new_image(filename) 245 246 fwids = {} 247 for section in handler.fv_sections: 248 fwid = handler.get_section_fwid(section) 249 if fwid is not None: 250 fwids[section] = fwid 251 return fwids 252 253 def modify_fwids(self, target='bios', sections=None): 254 """Modify the fwid in the image, but don't flash it. 255 256 @param target: the image type to modify: 'bios' (default) or 'ec' 257 @param sections: section(s) to modify. Default: A for bios, RW for ec 258 @return: fwids for the modified sections, as {section: fwid} 259 260 @type target: str 261 @type sections: tuple | list 262 @rtype: dict 263 """ 264 if sections is None: 265 sections = [self._get_default_section(target)] 266 267 image_fullpath = self._get_image_path(target) 268 if target == 'ec' and not os.path.isfile(image_fullpath): 269 # If the EC image is missing, report a specific error message. 270 raise FirmwareUpdaterError("Shellball does not contain ec.bin") 271 272 handler = self._get_handler(target) 273 fwids = handler.modify_fwids(sections) 274 275 handler.dump_whole(image_fullpath) 276 handler.new_image(image_fullpath) 277 278 return fwids 279 280 def modify_ecid_and_flash_to_bios(self): 281 """Modify ecid, put it to AP firmware, and flash it to the system. 282 283 This method is used for testing EC software sync for EC EFS (Early 284 Firmware Selection). It creates a slightly different EC RW image 285 (a different EC fwid) in AP firmware, in order to trigger EC 286 software sync on the next boot (a different hash with the original 287 EC RW). 288 289 The steps of this method: 290 * Modify the EC fwid by appending a '~', like from 291 'fizz_v1.1.7374-147f1bd64' to 'fizz_v1.1.7374-147f1bd64~'. 292 * Resign the EC image. 293 * Store the modififed EC RW image to CBFS component 'ecrw' of the 294 AP firmware's FW_MAIN_A and FW_MAIN_B, and also the new hash. 295 * Resign the AP image. 296 * Flash the modified AP image back to the system. 297 """ 298 self.cbfs_setup_work_dir() 299 300 fwid = self.get_section_fwid('ec', 'rw') 301 if fwid.endswith('~'): 302 raise FirmwareUpdaterError('The EC fwid is already modified') 303 304 # Modify the EC FWID and resign 305 fwid = fwid[:-1] + '~' 306 ec = self._get_handler('ec') 307 ec.set_section_fwid('rw', fwid) 308 ec.resign_ec_rwsig() 309 310 # Replace ecrw to the new one 311 ecrw_bin_path = os.path.join(self._cbfs_work_path, 312 chip_utils.ecrw.cbfs_bin_name) 313 ec.dump_section_body('rw', ecrw_bin_path) 314 315 # Replace ecrw.hash to the new one 316 ecrw_hash_path = os.path.join(self._cbfs_work_path, 317 chip_utils.ecrw.cbfs_hash_name) 318 with open(ecrw_hash_path, 'w') as f: 319 f.write(self.get_ec_hash()) 320 321 # Store the modified ecrw and its hash to cbfs 322 self.cbfs_replace_chip(chip_utils.ecrw.fw_name, extension='') 323 324 # Resign and flash the AP firmware back to the system 325 self.cbfs_sign_and_flash() 326 327 def corrupt_diagnostics_image(self, local_filename): 328 """Corrupts a diagnostics image in the CBFS working directory. 329 330 @param local_filename: Filename for storing the diagnostics image in the 331 CBFS working directory 332 """ 333 local_path = os.path.join(self._cbfs_work_path, local_filename) 334 335 # Invert the last few bytes of the image. Note that cbfstool will 336 # silently ignore bytes added after the end of the ELF, and it will 337 # refuse to use an ELF with noticeably corrupted headers as a payload. 338 num_bytes = 4 339 with open(local_path, 'rb+') as image: 340 image.seek(-num_bytes, os.SEEK_END) 341 last_bytes = array.array('B') 342 last_bytes.fromfile(image, num_bytes) 343 344 for i in range(len(last_bytes)): 345 last_bytes[i] = last_bytes[i] ^ 0xff 346 347 image.seek(-num_bytes, os.SEEK_END) 348 last_bytes.tofile(image) 349 350 def resign_firmware(self, version=None, work_path=None): 351 """Resign firmware with version. 352 353 Args: 354 version: new firmware version number, default to no modification. 355 work_path: work path, default to the updater work path. 356 """ 357 if work_path is None: 358 work_path = self._work_path 359 self.os_if.run_shell_command( 360 '/usr/share/vboot/bin/resign_firmwarefd.sh ' 361 '%s %s %s %s %s %s %s %s' % 362 (os.path.join(work_path, self._bios_path), 363 os.path.join(self._temp_path, 'output.bin'), 364 os.path.join(self._keys_path, 'firmware_data_key.vbprivk'), 365 os.path.join(self._keys_path, 'firmware.keyblock'), 366 os.path.join(self._keys_path, 367 'dev_firmware_data_key.vbprivk'), 368 os.path.join(self._keys_path, 'dev_firmware.keyblock'), 369 os.path.join(self._keys_path, 'kernel_subkey.vbpubk'), 370 ('%d' % version) if version is not None else '')) 371 self.os_if.copy_file( 372 '%s' % os.path.join(self._temp_path, 'output.bin'), 373 '%s' % os.path.join(work_path, self._bios_path)) 374 375 def _read_manifest(self, shellball=None): 376 """This gets the manifest from the shellball or the extracted directory. 377 378 @param shellball: Path of the shellball to read from (via --manifest). 379 If None (default), read from extracted manifest.json. 380 @return: the manifest information, or None 381 382 @type shellball: str | None 383 @rtype: dict 384 """ 385 386 if shellball: 387 output = self.os_if.run_shell_command_get_output( 388 'sh %s --manifest' % shellball) 389 manifest_text = '\n'.join(output or []) 390 else: 391 manifest_file = os.path.join(self._work_path, 'manifest.json') 392 manifest_text = self.os_if.read_file(manifest_file) 393 394 if manifest_text: 395 return json.loads(manifest_text) 396 else: 397 # TODO(dgoyette): Perhaps raise an exception for empty manifest? 398 return None 399 400 def _detect_image_paths(self, shellball=None): 401 """Scans shellball manifest to find correct bios and ec image paths. 402 403 @param shellball: Path of the shellball to read from (via --manifest). 404 If None (default), read from extracted manifest.json. 405 @type shellball: str | None 406 """ 407 model_name = cros_config.call_cros_config_get_output( 408 '/ name', self.os_if.run_shell_command_get_result) 409 410 if not model_name: 411 return 412 413 manifest = self._read_manifest(shellball) 414 415 if manifest: 416 model_info = manifest.get(model_name) 417 if model_info: 418 419 try: 420 self._bios_path = model_info['host']['image'] 421 except KeyError: 422 pass 423 424 try: 425 self._ec_path = model_info['ec']['image'] 426 except KeyError: 427 pass 428 429 def extract_shellball(self, append=None): 430 """Extract the working shellball. 431 432 Args: 433 append: decide which shellball to use with format 434 chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate' 435 if append is None. 436 Returns: 437 string: the full path of the shellball 438 """ 439 working_shellball = os.path.join(self._temp_path, 440 'chromeos-firmwareupdate') 441 if append: 442 working_shellball = working_shellball + '-%s' % append 443 444 self.os_if.run_shell_command( 445 'sh %s --unpack %s' % (working_shellball, self._work_path)) 446 447 # use the json file that was extracted, to catch extraction problems. 448 self._detect_image_paths() 449 return working_shellball 450 451 def repack_shellball(self, append=None): 452 """Repack shellball with new fwid. 453 454 New fwid follows the rule: [orignal_fwid]-[append]. 455 456 Args: 457 append: save the new shellball with a suffix, for example, 458 chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate' 459 if append is None. 460 Returns: 461 string: The full path to the shellball 462 """ 463 464 working_shellball = os.path.join(self._temp_path, 465 'chromeos-firmwareupdate') 466 if append: 467 new_shellball = working_shellball + '-%s' % append 468 self.os_if.copy_file(working_shellball, new_shellball) 469 working_shellball = new_shellball 470 471 self.os_if.run_shell_command( 472 'sh %s --repack %s' % (working_shellball, self._work_path)) 473 474 # use the shellball that was repacked, to catch repacking problems. 475 self._detect_image_paths(working_shellball) 476 return working_shellball 477 478 def reset_shellball(self): 479 """Extract shellball, then revert the AP and EC handlers' data.""" 480 self._create_temp_dir() 481 self.extract_shellball() 482 self.reload_images() 483 484 def reload_images(self): 485 """Reload handlers from the on-disk images, in case they've changed.""" 486 bios_file = os.path.join(self._work_path, self._bios_path) 487 self._real_bios_handler.deinit() 488 self._real_bios_handler.init(bios_file) 489 if self._real_ec_handler.is_available(): 490 ec_file = os.path.join(self._work_path, self._ec_path) 491 self._real_ec_handler.deinit() 492 self._real_ec_handler.init(ec_file, allow_fallback=True) 493 494 def run_firmwareupdate(self, mode, append=None, options=None): 495 """Do firmwareupdate with updater in temp_dir. 496 497 @param append: decide which shellball to use with format 498 chromeos-firmwareupdate-[append]. 499 Use'chromeos-firmwareupdate' if append is None. 500 @param mode: ex.'autoupdate', 'recovery', 'bootok', 'factory_install'... 501 @param options: ex. ['--noupdate_ec', '--force'] or [] or None. 502 503 @type append: str 504 @type mode: str 505 @type options: list | tuple | None 506 """ 507 if mode == 'bootok': 508 # Since CL:459837, bootok is moved to chromeos-setgoodfirmware. 509 set_good_cmd = '/usr/sbin/chromeos-setgoodfirmware' 510 if os.path.isfile(set_good_cmd): 511 return self.os_if.run_shell_command_get_status(set_good_cmd) 512 513 updater = os.path.join(self._temp_path, 'chromeos-firmwareupdate') 514 if append: 515 updater = '%s-%s' % (updater, append) 516 517 if options is None: 518 options = [] 519 if isinstance(options, tuple): 520 options = list(options) 521 522 def _has_emulate(option): 523 return option == '--emulate' or option.startswith('--emulate=') 524 525 if self.os_if.test_mode and not filter(_has_emulate, options): 526 # if in test mode, forcibly use --emulate, if not already used. 527 fake_bios = os.path.join(self._temp_path, 'rpc-test-fake-bios.bin') 528 if not os.path.exists(fake_bios): 529 bios_reader = self._create_handler('bios', 'tmp') 530 bios_reader.dump_flash(fake_bios) 531 options = ['--emulate', fake_bios] + options 532 533 update_cmd = '/bin/sh %s --mode %s %s' % (updater, mode, 534 ' '.join(options)) 535 536 return self.os_if.run_shell_command_get_status(update_cmd) 537 538 def cbfs_setup_work_dir(self): 539 """Sets up cbfs on DUT. 540 541 Finds bios.bin on the DUT and sets up a temp dir to operate on 542 bios.bin. If a bios.bin was specified, it is copied to the DUT 543 and used instead of the native bios.bin. 544 545 Returns: 546 The cbfs work directory path. 547 """ 548 549 self.os_if.remove_dir(self._cbfs_work_path) 550 self.os_if.copy_dir(self._work_path, self._cbfs_work_path) 551 552 return self._cbfs_work_path 553 554 def cbfs_extract_chip(self, fw_name, extension='.bin'): 555 """Extracts chip firmware blob from cbfs. 556 557 For a given chip type, looks for the corresponding firmware 558 blob and hash in the specified bios. The firmware blob and 559 hash are extracted into self._cbfs_work_path. 560 561 The extracted blobs will be <fw_name><extension> and 562 <fw_name>.hash located in cbfs_work_path. 563 564 Args: 565 fw_name: Chip firmware name to be extracted. 566 extension: Extension of the name of the cbfs component. 567 568 Returns: 569 Boolean success status. 570 """ 571 572 bios = os.path.join(self._cbfs_work_path, self._bios_path) 573 fw = fw_name 574 cbfs_extract = '%s %s extract -r FW_MAIN_A -n %s%%s -f %s%%s' % ( 575 self.CBFSTOOL, bios, fw, os.path.join(self._cbfs_work_path, 576 fw)) 577 578 cmd = cbfs_extract % (extension, extension) 579 if self.os_if.run_shell_command_get_status(cmd) != 0: 580 return False 581 582 cmd = cbfs_extract % ('.hash', '.hash') 583 if self.os_if.run_shell_command_get_status(cmd) != 0: 584 return False 585 586 return True 587 588 def cbfs_extract_diagnostics(self, diag_name, local_filename): 589 """Runs cbfstool to extract a diagnostics image. 590 591 @param diag_name: Name of the diagnostics image in CBFS 592 @param local_filename: Filename for storing the diagnostics image in the 593 CBFS working directory 594 """ 595 bios_path = os.path.join(self._cbfs_work_path, self._bios_path) 596 cbfs_extract = '%s %s extract -m x86 -r RW_LEGACY -n %s -f %s' % ( 597 self.CBFSTOOL, bios_path, diag_name, 598 os.path.join(self._cbfs_work_path, local_filename)) 599 600 self.os_if.run_shell_command(cbfs_extract) 601 602 def cbfs_get_chip_hash(self, fw_name): 603 """Returns chip firmware hash blob. 604 605 For a given chip type, returns the chip firmware hash blob. 606 Before making this request, the chip blobs must have been 607 extracted from cbfs using cbfs_extract_chip(). 608 The hash data is returned as hexadecimal string. 609 610 @param fw_name: Chip firmware name whose hash blob to get. 611 @return: Boolean success status. 612 @raise error.CmdError: Underlying remote shell operations failed. 613 """ 614 615 hexdump_cmd = '%s %s.hash' % ( 616 self.HEXDUMP, os.path.join(self._cbfs_work_path, fw_name)) 617 hashblob = self.os_if.run_shell_command_get_output(hexdump_cmd) 618 return hashblob 619 620 def cbfs_replace_chip(self, fw_name, extension='.bin'): 621 """Replaces chip firmware in CBFS (bios.bin). 622 623 For a given chip type, replaces its firmware blob and hash in 624 bios.bin. All files referenced are expected to be in the 625 directory set up using cbfs_setup_work_dir(). 626 627 @param fw_name: Chip firmware name to be replaced. 628 @param extension: Extension of the name of the cbfs component. 629 @return: Boolean success status. 630 @raise error.CmdError: Underlying remote shell operations failed. 631 """ 632 633 bios = os.path.join(self._cbfs_work_path, self._bios_path) 634 rm_hash_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s.hash' % ( 635 self.CBFSTOOL, bios, fw_name) 636 rm_bin_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s%s' % ( 637 self.CBFSTOOL, bios, fw_name, extension) 638 expand_cmd = '%s %s expand -r FW_MAIN_A,FW_MAIN_B' % (self.CBFSTOOL, 639 bios) 640 add_hash_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c none ' 641 '-f %s.hash -n %s.hash') % ( 642 self.CBFSTOOL, bios, 643 os.path.join(self._cbfs_work_path, 644 fw_name), fw_name) 645 add_bin_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c lzma ' 646 '-f %s%s -n %s%s') % ( 647 self.CBFSTOOL, bios, 648 os.path.join(self._cbfs_work_path, fw_name), 649 extension, fw_name, extension) 650 truncate_cmd = '%s %s truncate -r FW_MAIN_A,FW_MAIN_B' % ( 651 self.CBFSTOOL, bios) 652 653 self.os_if.run_shell_command(rm_hash_cmd) 654 self.os_if.run_shell_command(rm_bin_cmd) 655 try: 656 self.os_if.run_shell_command(expand_cmd) 657 except error.CmdError: 658 self.os_if.log( 659 ('%s may be too old, ' 660 'continuing without "expand" support') % self.CBFSTOOL) 661 662 self.os_if.run_shell_command(add_hash_cmd) 663 self.os_if.run_shell_command(add_bin_cmd) 664 try: 665 self.os_if.run_shell_command(truncate_cmd) 666 except error.CmdError: 667 self.os_if.log( 668 ('%s may be too old, ' 669 'continuing without "truncate" support') % self.CBFSTOOL) 670 671 return True 672 673 def cbfs_replace_diagnostics(self, diag_name, local_filename): 674 """Runs cbfstool to replace a diagnostics image in the firmware image. 675 676 @param diag_name: Name of the diagnostics image in CBFS 677 @param local_filename: Filename for storing the diagnostics image in the 678 CBFS working directory 679 """ 680 bios_path = os.path.join(self._cbfs_work_path, self._bios_path) 681 rm_cmd = '%s %s remove -r RW_LEGACY -n %s' % ( 682 self.CBFSTOOL, bios_path, diag_name) 683 expand_cmd = '%s %s expand -r RW_LEGACY' % (self.CBFSTOOL, bios_path) 684 add_cmd = ('%s %s add-payload -r RW_LEGACY -c lzma -n %s -f %s') % ( 685 self.CBFSTOOL, bios_path, diag_name, 686 os.path.join(self._cbfs_work_path, local_filename)) 687 truncate_cmd = '%s %s truncate -r RW_LEGACY' % ( 688 self.CBFSTOOL, bios_path) 689 690 self.os_if.run_shell_command(rm_cmd) 691 692 try: 693 self.os_if.run_shell_command(expand_cmd) 694 except error.CmdError: 695 self.os_if.log( 696 '%s may be too old, continuing without "expand" support' 697 % self.CBFSTOOL) 698 699 self.os_if.run_shell_command(add_cmd) 700 701 try: 702 self.os_if.run_shell_command(truncate_cmd) 703 except error.CmdError: 704 self.os_if.log( 705 '%s may be too old, continuing without "truncate" support' 706 % self.CBFSTOOL) 707 708 def cbfs_sign_and_flash(self): 709 """Signs CBFS (bios.bin) and flashes it.""" 710 self.resign_firmware(work_path=self._cbfs_work_path) 711 bios = self._get_handler('bios') 712 bios.new_image(os.path.join(self._cbfs_work_path, self._bios_path)) 713 bios.write_whole() 714 return True 715 716 def copy_bios(self, filename): 717 """Copy the shellball BIOS to the given name in the temp dir 718 719 @param filename: the filename to use for the copy 720 @return: the full path of the BIOS 721 722 @type filename: str 723 @rtype: str 724 """ 725 if not isinstance(filename, basestring): 726 raise FirmwareUpdaterError( 727 "Filename must be a string: %s" % repr(filename)) 728 src_bios = os.path.join(self._work_path, self._bios_path) 729 dst_bios = os.path.join(self._temp_path, filename) 730 self.os_if.copy_file(src_bios, dst_bios) 731 return dst_bios 732 733 def get_temp_path(self): 734 """Get temp directory path.""" 735 return self._temp_path 736 737 def get_keys_path(self): 738 """Get keys directory path.""" 739 return self._keys_path 740 741 def get_work_path(self): 742 """Get work directory path.""" 743 return self._work_path 744 745 def get_bios_relative_path(self): 746 """Gets the relative path of the bios image in the shellball.""" 747 return self._bios_path 748 749 def get_ec_relative_path(self): 750 """Gets the relative path of the ec image in the shellball.""" 751 return self._ec_path 752 753 def get_image_gbb_flags(self, filename=None): 754 """Get the GBB flags in the given image (shellball image if unspecified) 755 756 @param filename: the image path to act on (None to use shellball image) 757 @return: An integer of the GBB flags. 758 """ 759 if filename: 760 filename = os.path.join(self._temp_path, filename) 761 handler = self._create_handler('bios', 'image') 762 handler.new_image(filename) 763 else: 764 handler = self._get_handler('bios') 765 return handler.get_gbb_flags() 766 767 def set_image_gbb_flags(self, flags, filename=None): 768 """Set the GBB flags in the given image (shellball image if unspecified) 769 770 @param flags: the flags to set 771 @param filename: the image path to act on (None to use shellball image) 772 773 @type flags: int 774 @type filename: str | None 775 """ 776 if filename: 777 filename = os.path.join(self._temp_path, filename) 778 handler = self._create_handler('bios', 'image') 779 handler.new_image(filename) 780 else: 781 filename = self._get_image_path('bios') 782 handler = self._get_handler('bios') 783 handler.set_gbb_flags(flags) 784 handler.dump_whole(filename) 785