blob: bec0c6f26a3f1f4a478bb21e8da9037dd208d160 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001# 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
17import os
18import time
19import tarfile
20import tempfile
21
22from devlib.module import FlashModule
23from devlib.exception import HostError
24from devlib.utils.android import fastboot_flash_partition, fastboot_command
25from devlib.utils.misc import merge_dicts
26
27
28class 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
109def 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
116def 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