1#!/usr/bin/env python 2# Copyright (C) 2021 HiHope Open Source Organization . 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 17 18from argparse import ArgumentParser, FileType, Action 19from hashlib import sha1 20from os import fstat 21import re 22from struct import pack 23 24 25BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 26 27def filesize(f): 28 if f is None: 29 return 0 30 try: 31 return fstat(f.fileno()).st_size 32 except OSError: 33 return 0 34 35 36def update_sha(sha, f): 37 if f: 38 sha.update(f.read()) 39 f.seek(0) 40 sha.update(pack('I', filesize(f))) 41 else: 42 sha.update(pack('I', 0)) 43 44 45def pad_file(f, padding): 46 pad = (padding - (f.tell() & (padding - 1))) & (padding - 1) 47 f.write(pack(str(pad) + 'x')) 48 49 50def get_number_of_pages(image_size, page_size): 51 """calculates the number of pages required for the image""" 52 return (image_size + page_size - 1) / page_size 53 54 55def get_recovery_dtbo_offset(args): 56 """calculates the offset of recovery_dtbo image in the boot image""" 57 num_header_pages = 1 # header occupies a page 58 num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize) 59 num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize) 60 num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize) 61 dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages + 62 num_ramdisk_pages + num_second_pages) 63 return dtbo_offset 64 65 66def write_header_v3(args): 67 BOOT_IMAGE_HEADER_V3_SIZE = 1580 68 BOOT_MAGIC = 'ANDROID!'.encode() 69 70 args.output.write(pack('8s', BOOT_MAGIC)) 71 args.output.write(pack( 72 '4I', 73 filesize(args.kernel), # kernel size in bytes 74 filesize(args.ramdisk), # ramdisk size in bytes 75 (args.os_version << 11) | args.os_patch_level, # os version and patch level 76 BOOT_IMAGE_HEADER_V3_SIZE)) 77 78 args.output.write(pack('4I', 0, 0, 0, 0)) # reserved 79 80 args.output.write(pack('I', args.header_version)) # version of bootimage header 81 args.output.write(pack('1536s', args.cmdline.encode())) 82 pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE) 83 84def write_vendor_boot_header(args): 85 VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 86 BOOT_MAGIC = 'VNDRBOOT'.encode() 87 88 args.vendor_boot.write(pack('8s', BOOT_MAGIC)) 89 args.vendor_boot.write(pack( 90 '5I', 91 args.header_version, # version of header 92 args.pagesize, # flash page size we assume 93 args.base + args.kernel_offset, # kernel physical load addr 94 args.base + args.ramdisk_offset, # ramdisk physical load addr 95 filesize(args.vendor_ramdisk))) # vendor ramdisk size in bytes 96 args.vendor_boot.write(pack('2048s', args.vendor_cmdline.encode())) 97 args.vendor_boot.write(pack('I', args.base + args.tags_offset)) # physical addr for kernel tags 98 args.vendor_boot.write(pack('16s', args.board.encode())) # asciiz product name 99 args.vendor_boot.write(pack('I', VENDOR_BOOT_IMAGE_HEADER_V3_SIZE)) # header size in bytes 100 if filesize(args.dtb) == 0: 101 raise ValueError("DTB image must not be empty.") 102 args.vendor_boot.write(pack('I', filesize(args.dtb))) # size in bytes 103 args.vendor_boot.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address 104 pad_file(args.vendor_boot, args.pagesize) 105 106def write_header(args): 107 BOOT_IMAGE_HEADER_V1_SIZE = 1648 108 BOOT_IMAGE_HEADER_V2_SIZE = 1660 109 BOOT_MAGIC = 'ANDROID!'.encode() 110 111 if args.header_version > 3: 112 raise ValueError('Boot header version %d not supported' % args.header_version) 113 elif args.header_version == 3: 114 return write_header_v3(args) 115 116 args.output.write(pack('8s', BOOT_MAGIC)) 117 final_ramdisk_offset = (args.base + args.ramdisk_offset) if filesize(args.ramdisk) > 0 else 0 118 final_second_offset = (args.base + args.second_offset) if filesize(args.second) > 0 else 0 119 args.output.write(pack( 120 '10I', 121 filesize(args.kernel), # size in bytes 122 args.base + args.kernel_offset, # physical load addr 123 filesize(args.ramdisk), # size in bytes 124 final_ramdisk_offset, # physical load addr 125 filesize(args.second), # size in bytes 126 final_second_offset, # physical load addr 127 args.base + args.tags_offset, # physical addr for kernel tags 128 args.pagesize, # flash page size we assume 129 args.header_version, # version of bootimage header 130 (args.os_version << 11) | args.os_patch_level)) # os version and patch level 131 args.output.write(pack('16s', args.board.encode())) # asciiz product name 132 args.output.write(pack('512s', args.cmdline[:512].encode())) 133 134 sha = sha1() 135 update_sha(sha, args.kernel) 136 update_sha(sha, args.ramdisk) 137 update_sha(sha, args.second) 138 139 if args.header_version > 0: 140 update_sha(sha, args.recovery_dtbo) 141 if args.header_version > 1: 142 update_sha(sha, args.dtb) 143 144 img_id = pack('32s', sha.digest()) 145 146 args.output.write(img_id) 147 args.output.write(pack('1024s', args.cmdline[512:].encode())) 148 149 if args.header_version > 0: 150 args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes 151 if args.recovery_dtbo: 152 args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset 153 else: 154 args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo 155 156 # Populate boot image header size for header versions 1 and 2. 157 if args.header_version == 1: 158 args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE)) 159 elif args.header_version == 2: 160 args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE)) 161 162 if args.header_version > 1: 163 164 # if filesize(args.dtb) == 0: 165 # raise ValueError("DTB image must not be empty.") 166 167 args.output.write(pack('I', filesize(args.dtb))) # size in bytes 168 args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address 169 pad_file(args.output, args.pagesize) 170 return img_id 171 172 173class ValidateStrLenAction(Action): 174 def __init__(self, option_strings, dest, nargs=None, **kwargs): 175 if 'maxlen' not in kwargs: 176 raise ValueError('maxlen must be set') 177 self.maxlen = int(kwargs['maxlen']) 178 del kwargs['maxlen'] 179 super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs) 180 181 def __call__(self, parser, namespace, values, option_string=None): 182 if len(values) > self.maxlen: 183 raise ValueError( 184 'String argument too long: max {0:d}, got {1:d}'.format(self.maxlen, len(values))) 185 setattr(namespace, self.dest, values) 186 187 188def write_padded_file(f_out, f_in, padding): 189 if f_in is None: 190 return 191 f_out.write(f_in.read()) 192 pad_file(f_out, padding) 193 194 195def parse_int(x): 196 return int(x, 0) 197 198 199def parse_os_version(x): 200 match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x) 201 if match: 202 a = int(match.group(1)) 203 b = c = 0 204 if match.lastindex >= 2: 205 b = int(match.group(2)) 206 if match.lastindex == 3: 207 c = int(match.group(3)) 208 # 7 bits allocated for each field 209 assert a < 128 210 assert b < 128 211 assert c < 128 212 return (a << 14) | (b << 7) | c 213 return 0 214 215 216def parse_os_patch_level(x): 217 match = re.search(r'^(\d{4})-(\d{2})(?:-(\d{2}))?', x) 218 if match: 219 y = int(match.group(1)) - 2000 220 m = int(match.group(2)) 221 # 7 bits allocated for the year, 4 bits for the month 222 assert 0 <= y < 128 223 assert 0 < m <= 12 224 return (y << 4) | m 225 return 0 226 227 228def parse_cmdline(): 229 parser = ArgumentParser() 230 parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb')) 231 parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb')) 232 parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb')) 233 parser.add_argument('--dtb', help='path to dtb', type=FileType('rb')) 234 recovery_dtbo_group = parser.add_mutually_exclusive_group() 235 recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO', 236 type=FileType('rb')) 237 recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO', 238 type=FileType('rb'), metavar='RECOVERY_ACPIO', 239 dest='recovery_dtbo') 240 parser.add_argument('--cmdline', help='extra arguments to be passed on the ' 241 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536) 242 parser.add_argument('--vendor_cmdline', 243 help='kernel command line arguments contained in vendor boot', 244 default='', action=ValidateStrLenAction, maxlen=2048) 245 parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000) 246 parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000) 247 parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, 248 default=0x01000000) 249 parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int, 250 default=0x00f00000) 251 parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000) 252 253 parser.add_argument('--os_version', help='operating system version', type=parse_os_version, 254 default=0) 255 parser.add_argument('--os_patch_level', help='operating system patch level', 256 type=parse_os_patch_level, default=0) 257 parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100) 258 parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction, 259 maxlen=16) 260 parser.add_argument('--pagesize', help='page size', type=parse_int, 261 choices=[2**i for i in range(11, 15)], default=2048) 262 parser.add_argument('--id', help='print the image ID on standard output', 263 action='store_true') 264 parser.add_argument('--header_version', help='boot image header version', type=parse_int, 265 default=0) 266 parser.add_argument('-o', '--output', help='output file name', type=FileType('wb')) 267 parser.add_argument('--vendor_boot', help='vendor boot output file name', type=FileType('wb')) 268 parser.add_argument('--vendor_ramdisk', help='path to the vendor ramdisk', type=FileType('rb')) 269 270 return parser.parse_args() 271 272 273def write_data(args, pagesize): 274 write_padded_file(args.output, args.kernel, pagesize) 275 write_padded_file(args.output, args.ramdisk, pagesize) 276 write_padded_file(args.output, args.second, pagesize) 277 278 if args.header_version > 0 and args.header_version < 3: 279 write_padded_file(args.output, args.recovery_dtbo, pagesize) 280 if args.header_version == 2: 281 write_padded_file(args.output, args.dtb, pagesize) 282 283 284def write_vendor_boot_data(args): 285 write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize) 286 write_padded_file(args.vendor_boot, args.dtb, args.pagesize) 287 288 289def main(): 290 args = parse_cmdline() 291 if args.vendor_boot is not None: 292 if args.header_version < 3: 293 raise ValueError('--vendor_boot not compatible with given header version') 294 if args.vendor_ramdisk is None: 295 raise ValueError('--vendor_ramdisk missing or invalid') 296 write_vendor_boot_header(args) 297 write_vendor_boot_data(args) 298 if args.output is not None: 299 if args.kernel is None: 300 raise ValueError('kernel must be supplied when creating a boot image') 301 if args.second is not None and args.header_version > 2: 302 raise ValueError('--second not compatible with given header version') 303 img_id = write_header(args) 304 if args.header_version > 2: 305 write_data(args, BOOT_IMAGE_HEADER_V3_PAGESIZE) 306 else: 307 write_data(args, args.pagesize) 308 if args.id and img_id is not None: 309 # Python 2's struct.pack returns a string, but py3 returns bytes. 310 if isinstance(img_id, str): 311 img_id = [ord(x) for x in img_id] 312 print('0x' + ''.join('{:02x}'.format(c) for c in img_id)) 313 314 315if __name__ == '__main__': 316 main() 317