Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | # Copyright 2014-2015 ARM Limited |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | # |
| 15 | |
| 16 | # pylint: disable=attribute-defined-outside-init |
| 17 | import os |
| 18 | import time |
| 19 | import tarfile |
| 20 | import tempfile |
| 21 | |
| 22 | from devlib.module import FlashModule |
| 23 | from devlib.exception import HostError |
| 24 | from devlib.utils.android import fastboot_flash_partition, fastboot_command |
| 25 | from devlib.utils.misc import merge_dicts |
| 26 | |
| 27 | |
| 28 | class FastbootFlashModule(FlashModule): |
| 29 | |
| 30 | name = 'fastboot' |
| 31 | description = """ |
| 32 | Enables automated flashing of images using the fastboot utility. |
| 33 | |
| 34 | To use this flasher, a set of image files to be flused are required. |
| 35 | In addition a mapping between partitions and image file is required. There are two ways |
| 36 | to specify those requirements: |
| 37 | |
| 38 | - Image mapping: In this mode, a mapping between partitions and images is given in the agenda. |
| 39 | - Image Bundle: In This mode a tarball is specified, which must contain all image files as well |
| 40 | as well as a partition file, named ``partitions.txt`` which contains the mapping between |
| 41 | partitions and images. |
| 42 | |
| 43 | The format of ``partitions.txt`` defines one mapping per line as such: :: |
| 44 | |
| 45 | kernel zImage-dtb |
| 46 | ramdisk ramdisk_image |
| 47 | |
| 48 | """ |
| 49 | |
| 50 | delay = 0.5 |
| 51 | partitions_file_name = 'partitions.txt' |
| 52 | |
| 53 | @staticmethod |
| 54 | def probe(target): |
| 55 | return target.os == 'android' |
| 56 | |
| 57 | def __call__(self, image_bundle=None, images=None, bootargs=None): |
| 58 | if bootargs: |
| 59 | raise ValueError('{} does not support boot configuration'.format(self.name)) |
| 60 | self.prelude_done = False |
| 61 | to_flash = {} |
| 62 | if image_bundle: # pylint: disable=access-member-before-definition |
| 63 | image_bundle = expand_path(image_bundle) |
| 64 | to_flash = self._bundle_to_images(image_bundle) |
| 65 | to_flash = merge_dicts(to_flash, images or {}, should_normalize=False) |
| 66 | for partition, image_path in to_flash.iteritems(): |
| 67 | self.logger.debug('flashing {}'.format(partition)) |
| 68 | self._flash_image(self.target, partition, expand_path(image_path)) |
| 69 | fastboot_command('reboot') |
| 70 | self.target.connect(timeout=180) |
| 71 | |
| 72 | def _validate_image_bundle(self, image_bundle): |
| 73 | if not tarfile.is_tarfile(image_bundle): |
| 74 | raise HostError('File {} is not a tarfile'.format(image_bundle)) |
| 75 | with tarfile.open(image_bundle) as tar: |
| 76 | files = [tf.name for tf in tar.getmembers()] |
| 77 | if not any(pf in files for pf in (self.partitions_file_name, '{}/{}'.format(files[0], self.partitions_file_name))): |
| 78 | HostError('Image bundle does not contain the required partition file (see documentation)') |
| 79 | |
| 80 | def _bundle_to_images(self, image_bundle): |
| 81 | """ |
| 82 | Extracts the bundle to a temporary location and creates a mapping between the contents of the bundle |
| 83 | and images to be flushed. |
| 84 | """ |
| 85 | self._validate_image_bundle(image_bundle) |
| 86 | extract_dir = tempfile.mkdtemp() |
| 87 | with tarfile.open(image_bundle) as tar: |
| 88 | tar.extractall(path=extract_dir) |
| 89 | files = [tf.name for tf in tar.getmembers()] |
| 90 | if self.partitions_file_name not in files: |
| 91 | extract_dir = os.path.join(extract_dir, files[0]) |
| 92 | partition_file = os.path.join(extract_dir, self.partitions_file_name) |
| 93 | return get_mapping(extract_dir, partition_file) |
| 94 | |
| 95 | def _flash_image(self, target, partition, image_path): |
| 96 | if not self.prelude_done: |
| 97 | self._fastboot_prelude(target) |
| 98 | fastboot_flash_partition(partition, image_path) |
| 99 | time.sleep(self.delay) |
| 100 | |
| 101 | def _fastboot_prelude(self, target): |
| 102 | target.reset(fastboot=True) |
| 103 | time.sleep(self.delay) |
| 104 | self.prelude_done = True |
| 105 | |
| 106 | |
| 107 | # utility functions |
| 108 | |
| 109 | def expand_path(original_path): |
| 110 | path = os.path.abspath(os.path.expanduser(original_path)) |
| 111 | if not os.path.exists(path): |
| 112 | raise HostError('{} does not exist.'.format(path)) |
| 113 | return path |
| 114 | |
| 115 | |
| 116 | def get_mapping(base_dir, partition_file): |
| 117 | mapping = {} |
| 118 | with open(partition_file) as pf: |
| 119 | for line in pf: |
| 120 | pair = line.split() |
| 121 | if len(pair) != 2: |
| 122 | HostError('partitions.txt is not properly formated') |
| 123 | image_path = os.path.join(base_dir, pair[1]) |
| 124 | if not os.path.isfile(expand_path(image_path)): |
| 125 | HostError('file {} was not found in the bundle or was misplaced'.format(pair[1])) |
| 126 | mapping[pair[0]] = image_path |
| 127 | return mapping |
| 128 | |