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