• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2015, The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import print_function
17from sys import argv, exit, stderr
18from argparse import ArgumentParser, FileType, Action
19from os import fstat
20from struct import pack
21from hashlib import sha1
22import sys
23import re
24
25def filesize(f):
26    if f is None:
27        return 0
28    try:
29        return fstat(f.fileno()).st_size
30    except OSError:
31        return 0
32
33
34def update_sha(sha, f):
35    if f:
36        sha.update(f.read())
37        f.seek(0)
38        sha.update(pack('I', filesize(f)))
39    else:
40        sha.update(pack('I', 0))
41
42
43def pad_file(f, padding):
44    pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)
45    f.write(pack(str(pad) + 'x'))
46
47
48def get_number_of_pages(image_size, page_size):
49    """calculates the number of pages required for the image"""
50    return (image_size + page_size - 1) / page_size
51
52
53def get_recovery_dtbo_offset(args):
54    """calculates the offset of recovery_dtbo image in the boot image"""
55    num_header_pages = 1 # header occupies a page
56    num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize)
57    num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize)
58    num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize)
59    dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages +
60                                   num_ramdisk_pages + num_second_pages)
61    return dtbo_offset
62
63
64def write_header(args):
65    BOOT_IMAGE_HEADER_V1_SIZE = 1648
66    BOOT_IMAGE_HEADER_V2_SIZE = 1660
67    BOOT_MAGIC = 'ANDROID!'.encode()
68
69    if (args.header_version > 2):
70        raise ValueError('Boot header version %d not supported' % args.header_version)
71
72    args.output.write(pack('8s', BOOT_MAGIC))
73    args.output.write(pack('10I',
74        filesize(args.kernel),                          # size in bytes
75        args.base + args.kernel_offset,                 # physical load addr
76        filesize(args.ramdisk),                         # size in bytes
77        args.base + args.ramdisk_offset,                # physical load addr
78        filesize(args.second),                          # size in bytes
79        args.base + args.second_offset,                 # physical load addr
80        args.base + args.tags_offset,                   # physical addr for kernel tags
81        args.pagesize,                                  # flash page size we assume
82        args.header_version,                            # version of bootimage header
83        (args.os_version << 11) | args.os_patch_level)) # os version and patch level
84    args.output.write(pack('16s', args.board.encode())) # asciiz product name
85    args.output.write(pack('512s', args.cmdline[:512].encode()))
86
87    sha = sha1()
88    update_sha(sha, args.kernel)
89    update_sha(sha, args.ramdisk)
90    update_sha(sha, args.second)
91
92    if args.header_version > 0:
93        update_sha(sha, args.recovery_dtbo)
94    if args.header_version > 1:
95        update_sha(sha, args.dtb)
96
97    img_id = pack('32s', sha.digest())
98
99    args.output.write(img_id)
100    args.output.write(pack('1024s', args.cmdline[512:].encode()))
101
102    if args.header_version > 0:
103        args.output.write(pack('I', filesize(args.recovery_dtbo)))   # size in bytes
104        if args.recovery_dtbo:
105            args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset
106        else:
107            args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
108
109    # Populate boot image header size for header versions 1 and 2.
110    if args.header_version == 1:
111        args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE))
112    elif args.header_version == 2:
113        args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE))
114
115    if args.header_version > 1:
116        args.output.write(pack('I', filesize(args.dtb)))   # size in bytes
117        args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address
118    pad_file(args.output, args.pagesize)
119    return img_id
120
121
122class ValidateStrLenAction(Action):
123    def __init__(self, option_strings, dest, nargs=None, **kwargs):
124        if 'maxlen' not in kwargs:
125            raise ValueError('maxlen must be set')
126        self.maxlen = int(kwargs['maxlen'])
127        del kwargs['maxlen']
128        super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
129
130    def __call__(self, parser, namespace, values, option_string=None):
131        if len(values) > self.maxlen:
132            raise ValueError('String argument too long: max {0:d}, got {1:d}'.
133                format(self.maxlen, len(values)))
134        setattr(namespace, self.dest, values)
135
136
137def write_padded_file(f_out, f_in, padding):
138    if f_in is None:
139        return
140    f_out.write(f_in.read())
141    pad_file(f_out, padding)
142
143
144def parse_int(x):
145    return int(x, 0)
146
147def parse_os_version(x):
148    match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
149    if match:
150        a = int(match.group(1))
151        b = c = 0
152        if match.lastindex >= 2:
153            b = int(match.group(2))
154        if match.lastindex == 3:
155            c = int(match.group(3))
156        # 7 bits allocated for each field
157        assert a < 128
158        assert b < 128
159        assert c < 128
160        return (a << 14) | (b << 7) | c
161    return 0
162
163def parse_os_patch_level(x):
164    match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x)
165    if match:
166        y = int(match.group(1)) - 2000
167        m = int(match.group(2))
168        # 7 bits allocated for the year, 4 bits for the month
169        assert y >= 0 and y < 128
170        assert m > 0 and m <= 12
171        return (y << 4) | m
172    return 0
173
174def parse_cmdline():
175    parser = ArgumentParser()
176    parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),
177                        required=True)
178    parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
179    parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
180    parser.add_argument('--dtb', help='path to dtb', type=FileType('rb'))
181    recovery_dtbo_group = parser.add_mutually_exclusive_group()
182    recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb'))
183    recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO',
184                                     type=FileType('rb'), metavar='RECOVERY_ACPIO', dest='recovery_dtbo')
185    parser.add_argument('--cmdline', help='extra arguments to be passed on the '
186                        'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
187    parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
188    parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
189    parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
190    parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
191                        default=0x00f00000)
192    parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000)
193
194    parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
195                        default=0)
196    parser.add_argument('--os_patch_level', help='operating system patch level',
197                        type=parse_os_patch_level, default=0)
198    parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
199    parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
200                        maxlen=16)
201    parser.add_argument('--pagesize', help='page size', type=parse_int,
202                        choices=[2**i for i in range(11,15)], default=2048)
203    parser.add_argument('--id', help='print the image ID on standard output',
204                        action='store_true')
205    parser.add_argument('--header_version', help='boot image header version', type=parse_int, default=0)
206    parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
207                        required=True)
208    return parser.parse_args()
209
210
211def write_data(args):
212    write_padded_file(args.output, args.kernel, args.pagesize)
213    write_padded_file(args.output, args.ramdisk, args.pagesize)
214    write_padded_file(args.output, args.second, args.pagesize)
215
216    if args.header_version > 0:
217        write_padded_file(args.output, args.recovery_dtbo, args.pagesize)
218    if args.header_version > 1:
219        write_padded_file(args.output, args.dtb, args.pagesize)
220
221def main():
222    args = parse_cmdline()
223    img_id = write_header(args)
224    write_data(args)
225    if args.id:
226        if isinstance(img_id, str):
227            # Python 2's struct.pack returns a string, but py3 returns bytes.
228            img_id = [ord(x) for x in img_id]
229        print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
230
231if __name__ == '__main__':
232    main()
233