| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """A module to support automatic firmware update. |
| |
| See FirmwareUpdater object below. |
| """ |
| import array |
| import json |
| import os |
| |
| from autotest_lib.client.common_lib.cros import chip_utils |
| from autotest_lib.client.cros.faft.utils import (flashrom_handler, |
| shell_wrapper) |
| |
| |
| class FirmwareUpdaterError(Exception): |
| """Error in the FirmwareUpdater module.""" |
| |
| |
| class FirmwareUpdater(object): |
| """An object to support firmware update. |
| |
| This object will create a temporary directory in /var/tmp/faft/autest with |
| two subdirectory keys/ and work/. You can modify the keys in keys/ |
| directory. If you want to provide a given shellball to do firmware update, |
| put shellball under /var/tmp/faft/autest with name chromeos-firmwareupdate. |
| |
| @type os_if: autotest_lib.client.cros.faft.utils.os_interface.OSInterface |
| """ |
| |
| DAEMON = 'update-engine' |
| CBFSTOOL = 'cbfstool' |
| HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\'' |
| |
| DEFAULT_SHELLBALL = '/usr/sbin/chromeos-firmwareupdate' |
| DEFAULT_SUBDIR = 'autest' # subdirectory of /var/tmp/faft/ |
| DEFAULT_SECTION_FOR_TARGET = {'bios': 'a', 'ec': 'rw'} |
| |
| def __init__(self, os_if): |
| """Initialize the updater tools, but don't load the image data yet.""" |
| self.os_if = os_if |
| self._temp_path = self.os_if.state_dir_file(self.DEFAULT_SUBDIR) |
| self._cbfs_work_path = os.path.join(self._temp_path, 'cbfs') |
| self._keys_path = os.path.join(self._temp_path, 'keys') |
| self._work_path = os.path.join(self._temp_path, 'work') |
| self._bios_path = 'bios.bin' |
| self._ec_path = 'ec.bin' |
| |
| self.pubkey_path = os.path.join(self._keys_path, 'root_key.vbpubk') |
| self._real_bios_handler = self._create_handler('bios') |
| self._real_ec_handler = self._create_handler('ec') |
| self.initialized = False |
| |
| def init(self): |
| """Extract the shellball and other files, unless they already exist.""" |
| |
| if self.os_if.is_dir(self._work_path): |
| # If work dir is present, assume the whole temp dir is usable as-is. |
| self._detect_image_paths() |
| else: |
| # If work dir is missing, assume the whole temp dir is unusable, and |
| # recreate it. |
| self._create_temp_dir() |
| self.extract_shellball() |
| |
| self.initialized = True |
| |
| def _get_handler(self, target): |
| """Return the handler for the target, after initializing it if needed. |
| |
| @param target: image type ('bios' or 'ec') |
| @return: the handler for that target |
| |
| @type target: str |
| @rtype: flashrom_handler.FlashromHandler |
| """ |
| if target == 'bios': |
| if not self._real_bios_handler.initialized: |
| bios_file = self._get_image_path('bios') |
| self._real_bios_handler.init(bios_file) |
| return self._real_bios_handler |
| elif target == 'ec': |
| if not self._real_ec_handler.initialized: |
| ec_file = self._get_image_path('ec') |
| self._real_ec_handler.init(ec_file, allow_fallback=True) |
| return self._real_ec_handler |
| else: |
| raise FirmwareUpdaterError("Unhandled target: %r" % target) |
| |
| def _create_handler(self, target, suffix=None): |
| """Return a new (not pre-populated) handler for the given target, |
| such as for use in checking installed versions. |
| |
| @param target: image type ('bios' or 'ec') |
| @param suffix: additional piece for subdirectory of handler |
| Example: 'tmp' -> 'autest/<target>.tmp/' |
| @return: a new handler for that target |
| |
| @type target: str |
| @rtype: flashrom_handler.FlashromHandler |
| """ |
| if suffix: |
| subdir = '%s/%s.%s' % (self.DEFAULT_SUBDIR, target, suffix) |
| else: |
| subdir = '%s/%s' % (self.DEFAULT_SUBDIR, target) |
| return flashrom_handler.FlashromHandler( |
| self.os_if, self.pubkey_path, self._keys_path, target=target, |
| subdir=subdir) |
| |
| def _get_image_path(self, target): |
| """Return the handler for the given target |
| |
| @param target: image type ('bios' or 'ec') |
| @return: the path of the image file for that target |
| |
| @type target: str |
| @rtype: str |
| """ |
| if target == 'bios': |
| return os.path.join(self._work_path, self._bios_path) |
| elif target == 'ec': |
| return os.path.join(self._work_path, self._ec_path) |
| else: |
| raise FirmwareUpdaterError("Unhandled target: %r" % target) |
| |
| def _get_default_section(self, target): |
| """Return the default section to work with, for the given target |
| |
| @param target: image type ('bios' or 'ec') |
| @return: the default section for that target |
| |
| @type target: str |
| @rtype: str |
| """ |
| if target in self.DEFAULT_SECTION_FOR_TARGET: |
| return self.DEFAULT_SECTION_FOR_TARGET[target] |
| else: |
| raise FirmwareUpdaterError("Unhandled target: %r" % target) |
| |
| def _create_temp_dir(self): |
| """Create (or recreate) the temporary directory. |
| |
| The default /usr/sbin/chromeos-firmwareupdate is copied into _temp_dir, |
| and devkeys are copied to _key_path. The caller is responsible for |
| extracting the copied shellball. |
| """ |
| self.cleanup_temp_dir() |
| |
| self.os_if.create_dir(self._temp_path) |
| self.os_if.create_dir(self._cbfs_work_path) |
| self.os_if.create_dir(self._work_path) |
| self.os_if.copy_dir('/usr/share/vboot/devkeys', self._keys_path) |
| |
| working_shellball = os.path.join(self._temp_path, |
| 'chromeos-firmwareupdate') |
| self.os_if.copy_file(self.DEFAULT_SHELLBALL, working_shellball) |
| |
| def cleanup_temp_dir(self): |
| """Cleanup temporary directory.""" |
| if self.os_if.is_dir(self._temp_path): |
| self.os_if.remove_dir(self._temp_path) |
| |
| def stop_daemon(self): |
| """Stop update-engine daemon.""" |
| self.os_if.log('Stopping %s...' % self.DAEMON) |
| cmd = 'status %s | grep stop || stop %s' % (self.DAEMON, self.DAEMON) |
| self.os_if.run_shell_command(cmd) |
| |
| def start_daemon(self): |
| """Start update-engine daemon.""" |
| self.os_if.log('Starting %s...' % self.DAEMON) |
| cmd = 'status %s | grep start || start %s' % (self.DAEMON, self.DAEMON) |
| self.os_if.run_shell_command(cmd) |
| |
| def get_ec_hash(self): |
| """Retrieve the hex string of the EC hash.""" |
| ec = self._get_handler('ec') |
| return ec.get_section_hash('rw') |
| |
| def get_section_fwid(self, target='bios', section=None): |
| """Get one fwid from in-memory image, for the given target. |
| |
| @param target: the image type to get from: 'bios (default) or 'ec' |
| @param section: section to return. Default: A for bios, RW for EC |
| |
| @type target: str | None |
| @rtype: str |
| """ |
| if section is None: |
| section = self._get_default_section(target) |
| image_path = self._get_image_path(target) |
| if target == 'ec' and not os.path.isfile(image_path): |
| # If the EC image is missing, report a specific error message. |
| raise FirmwareUpdaterError("Shellball does not contain ec.bin") |
| |
| handler = self._get_handler(target) |
| handler.new_image(image_path) |
| fwid = handler.get_section_fwid(section) |
| if fwid is not None: |
| return str(fwid) |
| else: |
| return None |
| |
| def get_all_fwids(self, target='bios'): |
| """Get all non-empty fwids from in-memory image, for the given target. |
| |
| @param target: the image type to get from: 'bios' (default) or 'ec' |
| @return: fwid for the sections |
| |
| @type target: str |
| @rtype: dict | None |
| """ |
| image_path = self._get_image_path(target) |
| if target == 'ec' and not os.path.isfile(image_path): |
| # If the EC image is missing, report a specific error message. |
| raise FirmwareUpdaterError("Shellball does not contain ec.bin") |
| |
| handler = self._get_handler(target) |
| handler.new_image(image_path) |
| |
| fwids = {} |
| for section in handler.fv_sections: |
| fwid = handler.get_section_fwid(section) |
| if fwid is not None: |
| fwids[section] = fwid |
| return fwids |
| |
| def get_all_installed_fwids(self, target='bios', filename=None): |
| """Get all non-empty fwids from disk or flash, for the given target. |
| |
| @param target: the image type to get from: 'bios' (default) or 'ec' |
| @param filename: filename to read instead of using the actual flash |
| @return: fwid for the sections |
| |
| @type target: str |
| @type filename: str |
| @rtype: dict |
| """ |
| handler = self._create_handler(target, 'installed') |
| if filename: |
| filename = os.path.join(self._temp_path, filename) |
| handler.new_image(filename) |
| |
| fwids = {} |
| for section in handler.fv_sections: |
| fwid = handler.get_section_fwid(section) |
| if fwid is not None: |
| fwids[section] = fwid |
| return fwids |
| |
| def modify_fwids(self, target='bios', sections=None): |
| """Modify the fwid in the image, but don't flash it. |
| |
| @param target: the image type to modify: 'bios' (default) or 'ec' |
| @param sections: section(s) to modify. Default: A for bios, RW for ec |
| @return: fwids for the modified sections, as {section: fwid} |
| |
| @type target: str |
| @type sections: tuple | list |
| @rtype: dict |
| """ |
| if sections is None: |
| sections = [self._get_default_section(target)] |
| |
| image_fullpath = self._get_image_path(target) |
| if target == 'ec' and not os.path.isfile(image_fullpath): |
| # If the EC image is missing, report a specific error message. |
| raise FirmwareUpdaterError("Shellball does not contain ec.bin") |
| |
| handler = self._get_handler(target) |
| fwids = handler.modify_fwids(sections) |
| |
| handler.dump_whole(image_fullpath) |
| handler.new_image(image_fullpath) |
| |
| return fwids |
| |
| def modify_ecid_and_flash_to_bios(self): |
| """Modify ecid, put it to AP firmware, and flash it to the system. |
| |
| This method is used for testing EC software sync for EC EFS (Early |
| Firmware Selection). It creates a slightly different EC RW image |
| (a different EC fwid) in AP firmware, in order to trigger EC |
| software sync on the next boot (a different hash with the original |
| EC RW). |
| |
| The steps of this method: |
| * Modify the EC fwid by appending a '~', like from |
| 'fizz_v1.1.7374-147f1bd64' to 'fizz_v1.1.7374-147f1bd64~'. |
| * Resign the EC image. |
| * Store the modififed EC RW image to CBFS component 'ecrw' of the |
| AP firmware's FW_MAIN_A and FW_MAIN_B, and also the new hash. |
| * Resign the AP image. |
| * Flash the modified AP image back to the system. |
| """ |
| self.cbfs_setup_work_dir() |
| |
| fwid = self.get_section_fwid('ec', 'rw') |
| if fwid.endswith('~'): |
| raise FirmwareUpdaterError('The EC fwid is already modified') |
| |
| # Modify the EC FWID and resign |
| fwid = fwid[:-1] + '~' |
| ec = self._get_handler('ec') |
| ec.set_section_fwid('rw', fwid) |
| ec.resign_ec_rwsig() |
| |
| # Replace ecrw to the new one |
| ecrw_bin_path = os.path.join(self._cbfs_work_path, |
| chip_utils.ecrw.cbfs_bin_name) |
| ec.dump_section_body('rw', ecrw_bin_path) |
| |
| # Replace ecrw.hash to the new one |
| ecrw_hash_path = os.path.join(self._cbfs_work_path, |
| chip_utils.ecrw.cbfs_hash_name) |
| with open(ecrw_hash_path, 'w') as f: |
| f.write(self.get_ec_hash()) |
| |
| # Store the modified ecrw and its hash to cbfs |
| self.cbfs_replace_chip(chip_utils.ecrw.fw_name, extension='') |
| |
| # Resign and flash the AP firmware back to the system |
| self.cbfs_sign_and_flash() |
| |
| def corrupt_diagnostics_image(self, local_filename): |
| """Corrupts a diagnostics image in the CBFS working directory. |
| |
| @param local_filename: Filename for storing the diagnostics image in the |
| CBFS working directory |
| """ |
| local_path = os.path.join(self._cbfs_work_path, local_filename) |
| |
| # Invert the last few bytes of the image. Note that cbfstool will |
| # silently ignore bytes added after the end of the ELF, and it will |
| # refuse to use an ELF with noticeably corrupted headers as a payload. |
| num_bytes = 4 |
| with open(local_path, 'rb+') as image: |
| image.seek(-num_bytes, os.SEEK_END) |
| last_bytes = array.array('B') |
| last_bytes.fromfile(image, num_bytes) |
| |
| for i in range(len(last_bytes)): |
| last_bytes[i] = last_bytes[i] ^ 0xff |
| |
| image.seek(-num_bytes, os.SEEK_END) |
| last_bytes.tofile(image) |
| |
| def resign_firmware(self, version=None, work_path=None): |
| """Resign firmware with version. |
| |
| Args: |
| version: new firmware version number, default to no modification. |
| work_path: work path, default to the updater work path. |
| """ |
| if work_path is None: |
| work_path = self._work_path |
| self.os_if.run_shell_command( |
| '/usr/share/vboot/bin/resign_firmwarefd.sh ' |
| '%s %s %s %s %s %s %s %s' % |
| (os.path.join(work_path, self._bios_path), |
| os.path.join(self._temp_path, 'output.bin'), |
| os.path.join(self._keys_path, 'firmware_data_key.vbprivk'), |
| os.path.join(self._keys_path, 'firmware.keyblock'), |
| os.path.join(self._keys_path, |
| 'dev_firmware_data_key.vbprivk'), |
| os.path.join(self._keys_path, 'dev_firmware.keyblock'), |
| os.path.join(self._keys_path, 'kernel_subkey.vbpubk'), |
| ('%d' % version) if version is not None else '')) |
| self.os_if.copy_file( |
| '%s' % os.path.join(self._temp_path, 'output.bin'), |
| '%s' % os.path.join(work_path, self._bios_path)) |
| |
| def _read_manifest(self, shellball=None): |
| """This gets the manifest from the shellball or the extracted directory. |
| |
| @param shellball: Path of the shellball to read from (via --manifest). |
| If None (default), read from extracted manifest.json. |
| @return: the manifest information, or None |
| |
| @type shellball: str | None |
| @rtype: dict |
| """ |
| |
| if shellball: |
| output = self.os_if.run_shell_command_get_output( |
| 'sh %s --manifest' % shellball) |
| manifest_text = '\n'.join(output or []) |
| else: |
| manifest_file = os.path.join(self._work_path, 'manifest.json') |
| manifest_text = self.os_if.read_file(manifest_file) |
| |
| if manifest_text: |
| return json.loads(manifest_text) |
| else: |
| # TODO(dgoyette): Perhaps raise an exception for empty manifest? |
| return None |
| |
| def _detect_image_paths(self, shellball=None): |
| """Scans shellball manifest to find correct bios and ec image paths. |
| |
| @param shellball: Path of the shellball to read from (via --manifest). |
| If None (default), read from extracted manifest.json. |
| @type shellball: str | None |
| """ |
| model_result = self.os_if.run_shell_command_get_output( |
| 'mosys platform model') |
| |
| if not model_result: |
| return |
| |
| model_name = model_result[0] |
| |
| if not model_name: |
| return |
| |
| manifest = self._read_manifest(shellball) |
| |
| if manifest: |
| model_info = manifest.get(model_name) |
| if model_info: |
| |
| try: |
| self._bios_path = model_info['host']['image'] |
| except KeyError: |
| pass |
| |
| try: |
| self._ec_path = model_info['ec']['image'] |
| except KeyError: |
| pass |
| |
| def extract_shellball(self, append=None): |
| """Extract the working shellball. |
| |
| Args: |
| append: decide which shellball to use with format |
| chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate' |
| if append is None. |
| Returns: |
| string: the full path of the shellball |
| """ |
| working_shellball = os.path.join(self._temp_path, |
| 'chromeos-firmwareupdate') |
| if append: |
| working_shellball = working_shellball + '-%s' % append |
| |
| self.os_if.run_shell_command( |
| 'sh %s --sb_extract %s' % (working_shellball, self._work_path)) |
| |
| # use the json file that was extracted, to catch extraction problems. |
| self._detect_image_paths() |
| return working_shellball |
| |
| def repack_shellball(self, append=None): |
| """Repack shellball with new fwid. |
| |
| New fwid follows the rule: [orignal_fwid]-[append]. |
| |
| Args: |
| append: save the new shellball with a suffix, for example, |
| chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate' |
| if append is None. |
| Returns: |
| string: The full path to the shellball |
| """ |
| |
| working_shellball = os.path.join(self._temp_path, |
| 'chromeos-firmwareupdate') |
| if append: |
| new_shellball = working_shellball + '-%s' % append |
| self.os_if.copy_file(working_shellball, new_shellball) |
| working_shellball = new_shellball |
| |
| self.os_if.run_shell_command( |
| 'sh %s --sb_repack %s' % (working_shellball, self._work_path)) |
| |
| # use the shellball that was repacked, to catch repacking problems. |
| self._detect_image_paths(working_shellball) |
| return working_shellball |
| |
| def reset_shellball(self): |
| """Extract shellball, then revert the AP and EC handlers' data.""" |
| self._create_temp_dir() |
| self.extract_shellball() |
| self.reload_images() |
| |
| def reload_images(self): |
| """Reload handlers from the on-disk images, in case they've changed.""" |
| bios_file = os.path.join(self._work_path, self._bios_path) |
| self._real_bios_handler.deinit() |
| self._real_bios_handler.init(bios_file) |
| if self._real_ec_handler.is_available(): |
| ec_file = os.path.join(self._work_path, self._ec_path) |
| self._real_ec_handler.deinit() |
| self._real_ec_handler.init(ec_file, allow_fallback=True) |
| |
| def run_firmwareupdate(self, mode, append=None, options=None): |
| """Do firmwareupdate with updater in temp_dir. |
| |
| @param append: decide which shellball to use with format |
| chromeos-firmwareupdate-[append]. |
| Use'chromeos-firmwareupdate' if append is None. |
| @param mode: ex.'autoupdate', 'recovery', 'bootok', 'factory_install'... |
| @param options: ex. ['--noupdate_ec', '--force'] or [] or None. |
| |
| @type append: str |
| @type mode: str |
| @type options: list | tuple | None |
| """ |
| if mode == 'bootok': |
| # Since CL:459837, bootok is moved to chromeos-setgoodfirmware. |
| set_good_cmd = '/usr/sbin/chromeos-setgoodfirmware' |
| if os.path.isfile(set_good_cmd): |
| return self.os_if.run_shell_command_get_status(set_good_cmd) |
| |
| updater = os.path.join(self._temp_path, 'chromeos-firmwareupdate') |
| if append: |
| updater = '%s-%s' % (updater, append) |
| |
| if options is None: |
| options = [] |
| if isinstance(options, tuple): |
| options = list(options) |
| |
| def _has_emulate(option): |
| return option == '--emulate' or option.startswith('--emulate=') |
| |
| if self.os_if.test_mode and not filter(_has_emulate, options): |
| # if in test mode, forcibly use --emulate, if not already used. |
| fake_bios = os.path.join(self._temp_path, 'rpc-test-fake-bios.bin') |
| if not os.path.exists(fake_bios): |
| bios_reader = self._create_handler('bios', 'tmp') |
| bios_reader.dump_flash(fake_bios) |
| options = ['--emulate', fake_bios] + options |
| |
| update_cmd = '/bin/sh %s --mode %s %s' % (updater, mode, |
| ' '.join(options)) |
| |
| return self.os_if.run_shell_command_get_status(update_cmd) |
| |
| def cbfs_setup_work_dir(self): |
| """Sets up cbfs on DUT. |
| |
| Finds bios.bin on the DUT and sets up a temp dir to operate on |
| bios.bin. If a bios.bin was specified, it is copied to the DUT |
| and used instead of the native bios.bin. |
| |
| Returns: |
| The cbfs work directory path. |
| """ |
| |
| self.os_if.remove_dir(self._cbfs_work_path) |
| self.os_if.copy_dir(self._work_path, self._cbfs_work_path) |
| |
| return self._cbfs_work_path |
| |
| def cbfs_extract_chip(self, fw_name, extension='.bin'): |
| """Extracts chip firmware blob from cbfs. |
| |
| For a given chip type, looks for the corresponding firmware |
| blob and hash in the specified bios. The firmware blob and |
| hash are extracted into self._cbfs_work_path. |
| |
| The extracted blobs will be <fw_name><extension> and |
| <fw_name>.hash located in cbfs_work_path. |
| |
| Args: |
| fw_name: Chip firmware name to be extracted. |
| extension: Extension of the name of the cbfs component. |
| |
| Returns: |
| Boolean success status. |
| """ |
| |
| bios = os.path.join(self._cbfs_work_path, self._bios_path) |
| fw = fw_name |
| cbfs_extract = '%s %s extract -r FW_MAIN_A -n %s%%s -f %s%%s' % ( |
| self.CBFSTOOL, bios, fw, os.path.join(self._cbfs_work_path, |
| fw)) |
| |
| cmd = cbfs_extract % (extension, extension) |
| if self.os_if.run_shell_command_get_status(cmd) != 0: |
| return False |
| |
| cmd = cbfs_extract % ('.hash', '.hash') |
| if self.os_if.run_shell_command_get_status(cmd) != 0: |
| return False |
| |
| return True |
| |
| def cbfs_extract_diagnostics(self, diag_name, local_filename): |
| """Runs cbfstool to extract a diagnostics image. |
| |
| @param diag_name: Name of the diagnostics image in CBFS |
| @param local_filename: Filename for storing the diagnostics image in the |
| CBFS working directory |
| """ |
| bios_path = os.path.join(self._cbfs_work_path, self._bios_path) |
| cbfs_extract = '%s %s extract -m x86 -r RW_LEGACY -n %s -f %s' % ( |
| self.CBFSTOOL, bios_path, diag_name, |
| os.path.join(self._cbfs_work_path, local_filename)) |
| |
| self.os_if.run_shell_command(cbfs_extract) |
| |
| def cbfs_get_chip_hash(self, fw_name): |
| """Returns chip firmware hash blob. |
| |
| For a given chip type, returns the chip firmware hash blob. |
| Before making this request, the chip blobs must have been |
| extracted from cbfs using cbfs_extract_chip(). |
| The hash data is returned as hexadecimal string. |
| |
| Args: |
| fw_name: |
| Chip firmware name whose hash blob to get. |
| |
| Returns: |
| Boolean success status. |
| |
| Raises: |
| shell_wrapper.ShellError: Underlying remote shell |
| operations failed. |
| """ |
| |
| hexdump_cmd = '%s %s.hash' % ( |
| self.HEXDUMP, os.path.join(self._cbfs_work_path, fw_name)) |
| hashblob = self.os_if.run_shell_command_get_output(hexdump_cmd) |
| return hashblob |
| |
| def cbfs_replace_chip(self, fw_name, extension='.bin'): |
| """Replaces chip firmware in CBFS (bios.bin). |
| |
| For a given chip type, replaces its firmware blob and hash in |
| bios.bin. All files referenced are expected to be in the |
| directory set up using cbfs_setup_work_dir(). |
| |
| Args: |
| fw_name: Chip firmware name to be replaced. |
| extension: Extension of the name of the cbfs component. |
| |
| Returns: |
| Boolean success status. |
| |
| Raises: |
| shell_wrapper.ShellError: Underlying remote shell |
| operations failed. |
| """ |
| |
| bios = os.path.join(self._cbfs_work_path, self._bios_path) |
| rm_hash_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s.hash' % ( |
| self.CBFSTOOL, bios, fw_name) |
| rm_bin_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s%s' % ( |
| self.CBFSTOOL, bios, fw_name, extension) |
| expand_cmd = '%s %s expand -r FW_MAIN_A,FW_MAIN_B' % (self.CBFSTOOL, |
| bios) |
| add_hash_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c none ' |
| '-f %s.hash -n %s.hash') % ( |
| self.CBFSTOOL, bios, |
| os.path.join(self._cbfs_work_path, |
| fw_name), fw_name) |
| add_bin_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c lzma ' |
| '-f %s%s -n %s%s') % ( |
| self.CBFSTOOL, bios, |
| os.path.join(self._cbfs_work_path, fw_name), |
| extension, fw_name, extension) |
| truncate_cmd = '%s %s truncate -r FW_MAIN_A,FW_MAIN_B' % ( |
| self.CBFSTOOL, bios) |
| |
| self.os_if.run_shell_command(rm_hash_cmd) |
| self.os_if.run_shell_command(rm_bin_cmd) |
| try: |
| self.os_if.run_shell_command(expand_cmd) |
| except shell_wrapper.ShellError: |
| self.os_if.log( |
| ('%s may be too old, ' |
| 'continuing without "expand" support') % self.CBFSTOOL) |
| |
| self.os_if.run_shell_command(add_hash_cmd) |
| self.os_if.run_shell_command(add_bin_cmd) |
| try: |
| self.os_if.run_shell_command(truncate_cmd) |
| except shell_wrapper.ShellError: |
| self.os_if.log( |
| ('%s may be too old, ' |
| 'continuing without "truncate" support') % self.CBFSTOOL) |
| |
| return True |
| |
| def cbfs_replace_diagnostics(self, diag_name, local_filename): |
| """Runs cbfstool to replace a diagnostics image in the firmware image. |
| |
| @param diag_name: Name of the diagnostics image in CBFS |
| @param local_filename: Filename for storing the diagnostics image in the |
| CBFS working directory |
| """ |
| bios_path = os.path.join(self._cbfs_work_path, self._bios_path) |
| rm_cmd = '%s %s remove -r RW_LEGACY -n %s' % ( |
| self.CBFSTOOL, bios_path, diag_name) |
| expand_cmd = '%s %s expand -r RW_LEGACY' % (self.CBFSTOOL, bios_path) |
| add_cmd = ('%s %s add-payload -r RW_LEGACY -c lzma -n %s -f %s') % ( |
| self.CBFSTOOL, bios_path, diag_name, |
| os.path.join(self._cbfs_work_path, local_filename)) |
| truncate_cmd = '%s %s truncate -r RW_LEGACY' % ( |
| self.CBFSTOOL, bios_path) |
| |
| self.os_if.run_shell_command(rm_cmd) |
| |
| try: |
| self.os_if.run_shell_command(expand_cmd) |
| except shell_wrapper.ShellError: |
| self.os_if.log( |
| '%s may be too old, continuing without "expand" support' |
| % self.CBFSTOOL) |
| |
| self.os_if.run_shell_command(add_cmd) |
| |
| try: |
| self.os_if.run_shell_command(truncate_cmd) |
| except shell_wrapper.ShellError: |
| self.os_if.log( |
| '%s may be too old, continuing without "truncate" support' |
| % self.CBFSTOOL) |
| |
| def cbfs_sign_and_flash(self): |
| """Signs CBFS (bios.bin) and flashes it.""" |
| self.resign_firmware(work_path=self._cbfs_work_path) |
| bios = self._get_handler('bios') |
| bios.new_image(os.path.join(self._cbfs_work_path, self._bios_path)) |
| bios.write_whole() |
| return True |
| |
| def copy_bios(self, filename): |
| """Copy the shellball BIOS to the given name in the temp dir |
| |
| @param filename: the filename to use for the copy |
| @return: the full path of the BIOS |
| |
| @type filename: str |
| @rtype: str |
| """ |
| if not isinstance(filename, basestring): |
| raise FirmwareUpdaterError( |
| "Filename must be a string: %s" % repr(filename)) |
| src_bios = os.path.join(self._work_path, self._bios_path) |
| dst_bios = os.path.join(self._temp_path, filename) |
| self.os_if.copy_file(src_bios, dst_bios) |
| return dst_bios |
| |
| def get_temp_path(self): |
| """Get temp directory path.""" |
| return self._temp_path |
| |
| def get_keys_path(self): |
| """Get keys directory path.""" |
| return self._keys_path |
| |
| def get_work_path(self): |
| """Get work directory path.""" |
| return self._work_path |
| |
| def get_bios_relative_path(self): |
| """Gets the relative path of the bios image in the shellball.""" |
| return self._bios_path |
| |
| def get_ec_relative_path(self): |
| """Gets the relative path of the ec image in the shellball.""" |
| return self._ec_path |
| |
| def get_image_gbb_flags(self, filename=None): |
| """Get the GBB flags in the given image (shellball image if unspecified) |
| |
| @param filename: the image path to act on (None to use shellball image) |
| @return: An integer of the GBB flags. |
| """ |
| if filename: |
| filename = os.path.join(self._temp_path, filename) |
| handler = self._create_handler('bios', 'image') |
| handler.new_image(filename) |
| else: |
| handler = self._get_handler('bios') |
| return handler.get_gbb_flags() |
| |
| def set_image_gbb_flags(self, flags, filename=None): |
| """Set the GBB flags in the given image (shellball image if unspecified) |
| |
| @param flags: the flags to set |
| @param filename: the image path to act on (None to use shellball image) |
| |
| @type flags: int |
| @type filename: str | None |
| """ |
| if filename: |
| filename = os.path.join(self._temp_path, filename) |
| handler = self._create_handler('bios', 'image') |
| handler.new_image(filename) |
| else: |
| filename = self._bios_path |
| handler = self._get_handler('bios') |
| handler.set_gbb_flags(flags) |
| handler.dump_whole(filename) |