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