| # Copyright 2014-2015 ARM Limited |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| # pylint: disable=attribute-defined-outside-init |
| import os |
| import time |
| import tarfile |
| import tempfile |
| |
| from devlib.module import FlashModule |
| from devlib.exception import HostError |
| from devlib.utils.android import fastboot_flash_partition, fastboot_command |
| from devlib.utils.misc import merge_dicts |
| |
| |
| class FastbootFlashModule(FlashModule): |
| |
| name = 'fastboot' |
| description = """ |
| Enables automated flashing of images using the fastboot utility. |
| |
| To use this flasher, a set of image files to be flused are required. |
| In addition a mapping between partitions and image file is required. There are two ways |
| to specify those requirements: |
| |
| - Image mapping: In this mode, a mapping between partitions and images is given in the agenda. |
| - Image Bundle: In This mode a tarball is specified, which must contain all image files as well |
| as well as a partition file, named ``partitions.txt`` which contains the mapping between |
| partitions and images. |
| |
| The format of ``partitions.txt`` defines one mapping per line as such: :: |
| |
| kernel zImage-dtb |
| ramdisk ramdisk_image |
| |
| """ |
| |
| delay = 0.5 |
| partitions_file_name = 'partitions.txt' |
| |
| @staticmethod |
| def probe(target): |
| return target.os == 'android' |
| |
| def __call__(self, image_bundle=None, images=None, bootargs=None): |
| if bootargs: |
| raise ValueError('{} does not support boot configuration'.format(self.name)) |
| self.prelude_done = False |
| to_flash = {} |
| if image_bundle: # pylint: disable=access-member-before-definition |
| image_bundle = expand_path(image_bundle) |
| to_flash = self._bundle_to_images(image_bundle) |
| to_flash = merge_dicts(to_flash, images or {}, should_normalize=False) |
| for partition, image_path in to_flash.iteritems(): |
| self.logger.debug('flashing {}'.format(partition)) |
| self._flash_image(self.target, partition, expand_path(image_path)) |
| fastboot_command('reboot') |
| self.target.connect(timeout=180) |
| |
| def _validate_image_bundle(self, image_bundle): |
| if not tarfile.is_tarfile(image_bundle): |
| raise HostError('File {} is not a tarfile'.format(image_bundle)) |
| with tarfile.open(image_bundle) as tar: |
| files = [tf.name for tf in tar.getmembers()] |
| if not any(pf in files for pf in (self.partitions_file_name, '{}/{}'.format(files[0], self.partitions_file_name))): |
| HostError('Image bundle does not contain the required partition file (see documentation)') |
| |
| def _bundle_to_images(self, image_bundle): |
| """ |
| Extracts the bundle to a temporary location and creates a mapping between the contents of the bundle |
| and images to be flushed. |
| """ |
| self._validate_image_bundle(image_bundle) |
| extract_dir = tempfile.mkdtemp() |
| with tarfile.open(image_bundle) as tar: |
| tar.extractall(path=extract_dir) |
| files = [tf.name for tf in tar.getmembers()] |
| if self.partitions_file_name not in files: |
| extract_dir = os.path.join(extract_dir, files[0]) |
| partition_file = os.path.join(extract_dir, self.partitions_file_name) |
| return get_mapping(extract_dir, partition_file) |
| |
| def _flash_image(self, target, partition, image_path): |
| if not self.prelude_done: |
| self._fastboot_prelude(target) |
| fastboot_flash_partition(partition, image_path) |
| time.sleep(self.delay) |
| |
| def _fastboot_prelude(self, target): |
| target.reset(fastboot=True) |
| time.sleep(self.delay) |
| self.prelude_done = True |
| |
| |
| # utility functions |
| |
| def expand_path(original_path): |
| path = os.path.abspath(os.path.expanduser(original_path)) |
| if not os.path.exists(path): |
| raise HostError('{} does not exist.'.format(path)) |
| return path |
| |
| |
| def get_mapping(base_dir, partition_file): |
| mapping = {} |
| with open(partition_file) as pf: |
| for line in pf: |
| pair = line.split() |
| if len(pair) != 2: |
| HostError('partitions.txt is not properly formated') |
| image_path = os.path.join(base_dir, pair[1]) |
| if not os.path.isfile(expand_path(image_path)): |
| HostError('file {} was not found in the bundle or was misplaced'.format(pair[1])) |
| mapping[pair[0]] = image_path |
| return mapping |
| |