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 129