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