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