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 16 from __future__ import print_function 17 from sys import argv, exit, stderr 18 from argparse import ArgumentParser, FileType, Action 19 from os import fstat 20 from struct import pack 21 from hashlib import sha1 22 import sys 23 import re 24 25 def 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 34 def 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 43 def pad_file(f, padding): 44 pad = (padding - (f.tell() & (padding - 1))) & (padding - 1) 45 f.write(pack(str(pad) + 'x')) 46 47 48 def 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 53 def 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 64 def write_header(args): 65 BOOT_MAGIC = 'ANDROID!'.encode() 66 args.output.write(pack('8s', BOOT_MAGIC)) 67 args.output.write(pack('10I', 68 filesize(args.kernel), # size in bytes 69 args.base + args.kernel_offset, # physical load addr 70 filesize(args.ramdisk), # size in bytes 71 args.base + args.ramdisk_offset, # physical load addr 72 filesize(args.second), # size in bytes 73 args.base + args.second_offset, # physical load addr 74 args.base + args.tags_offset, # physical addr for kernel tags 75 args.pagesize, # flash page size we assume 76 args.header_version, # version of bootimage header 77 (args.os_version << 11) | args.os_patch_level)) # os version and patch level 78 args.output.write(pack('16s', args.board.encode())) # asciiz product name 79 args.output.write(pack('512s', args.cmdline[:512].encode())) 80 81 sha = sha1() 82 update_sha(sha, args.kernel) 83 update_sha(sha, args.ramdisk) 84 update_sha(sha, args.second) 85 86 if args.header_version > 0: 87 update_sha(sha, args.recovery_dtbo) 88 89 img_id = pack('32s', sha.digest()) 90 91 args.output.write(img_id) 92 args.output.write(pack('1024s', args.cmdline[512:].encode())) 93 94 if args.header_version > 0: 95 args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes 96 if args.recovery_dtbo: 97 args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset 98 else: 99 args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo 100 args.output.write(pack('I', args.output.tell() + 4)) # size of boot header 101 102 pad_file(args.output, args.pagesize) 103 return img_id 104 105 106 class ValidateStrLenAction(Action): 107 def __init__(self, option_strings, dest, nargs=None, **kwargs): 108 if 'maxlen' not in kwargs: 109 raise ValueError('maxlen must be set') 110 self.maxlen = int(kwargs['maxlen']) 111 del kwargs['maxlen'] 112 super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs) 113 114 def __call__(self, parser, namespace, values, option_string=None): 115 if len(values) > self.maxlen: 116 raise ValueError('String argument too long: max {0:d}, got {1:d}'. 117 format(self.maxlen, len(values))) 118 setattr(namespace, self.dest, values) 119 120 121 def write_padded_file(f_out, f_in, padding): 122 if f_in is None: 123 return 124 f_out.write(f_in.read()) 125 pad_file(f_out, padding) 126 127 128 def parse_int(x): 129 return int(x, 0) 130 131 def parse_os_version(x): 132 match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x) 133 if match: 134 a = int(match.group(1)) 135 b = c = 0 136 if match.lastindex >= 2: 137 b = int(match.group(2)) 138 if match.lastindex == 3: 139 c = int(match.group(3)) 140 # 7 bits allocated for each field 141 assert a < 128 142 assert b < 128 143 assert c < 128 144 return (a << 14) | (b << 7) | c 145 return 0 146 147 def parse_os_patch_level(x): 148 match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x) 149 if match: 150 y = int(match.group(1)) - 2000 151 m = int(match.group(2)) 152 # 7 bits allocated for the year, 4 bits for the month 153 assert y >= 0 and y < 128 154 assert m > 0 and m <= 12 155 return (y << 4) | m 156 return 0 157 158 def parse_cmdline(): 159 parser = ArgumentParser() 160 parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'), 161 required=True) 162 parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb')) 163 parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb')) 164 parser.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb')) 165 parser.add_argument('--cmdline', help='extra arguments to be passed on the ' 166 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536) 167 parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000) 168 parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000) 169 parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000) 170 parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int, 171 default=0x00f00000) 172 parser.add_argument('--os_version', help='operating system version', type=parse_os_version, 173 default=0) 174 parser.add_argument('--os_patch_level', help='operating system patch level', 175 type=parse_os_patch_level, default=0) 176 parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100) 177 parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction, 178 maxlen=16) 179 parser.add_argument('--pagesize', help='page size', type=parse_int, 180 choices=[2**i for i in range(11,15)], default=2048) 181 parser.add_argument('--id', help='print the image ID on standard output', 182 action='store_true') 183 parser.add_argument('--header_version', help='boot image header version', type=parse_int, default=0) 184 parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'), 185 required=True) 186 return parser.parse_args() 187 188 189 def write_data(args): 190 write_padded_file(args.output, args.kernel, args.pagesize) 191 write_padded_file(args.output, args.ramdisk, args.pagesize) 192 write_padded_file(args.output, args.second, args.pagesize) 193 194 if args.header_version > 0: 195 write_padded_file(args.output, args.recovery_dtbo, args.pagesize) 196 197 def main(): 198 args = parse_cmdline() 199 img_id = write_header(args) 200 write_data(args) 201 if args.id: 202 if isinstance(img_id, str): 203 # Python 2's struct.pack returns a string, but py3 returns bytes. 204 img_id = [ord(x) for x in img_id] 205 print('0x' + ''.join('{:02x}'.format(c) for c in img_id)) 206 207 if __name__ == '__main__': 208 main() 209