Home | History | Annotate | Download | only in module
      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