Home | History | Annotate | Download | only in mkbootimg
      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