blob: 00a46233e9366690044cf16e3860ac75fdc28859 [file] [log] [blame]
Rom Lemarchand4ee256c2015-05-19 16:58:40 -07001#!/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
Tao Baoa1494502019-09-17 10:02:40 -070017
Rom Lemarchand4ee256c2015-05-19 16:58:40 -070018from argparse import ArgumentParser, FileType, Action
Rom Lemarchand4ee256c2015-05-19 16:58:40 -070019from hashlib import sha1
Tao Baoa1494502019-09-17 10:02:40 -070020from os import fstat
Sami Tolvanenea649102016-03-14 09:08:59 -070021import re
Tao Baoa1494502019-09-17 10:02:40 -070022from struct import pack
23
Rom Lemarchand4ee256c2015-05-19 16:58:40 -070024
Steve Muckle769efcf2019-09-30 11:19:48 -070025BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
26
Rom Lemarchand4ee256c2015-05-19 16:58:40 -070027def 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
Hridya Valsarajuc1a18a32018-05-31 12:39:58 -070050def 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
Steve Muckle9a09efb2019-07-30 12:24:41 -070066def write_header_v3(args):
Steve Muckle41bec4c2020-04-13 12:12:29 -070067 BOOT_IMAGE_HEADER_V3_SIZE = 1580
Steve Muckle9a09efb2019-07-30 12:24:41 -070068 BOOT_MAGIC = 'ANDROID!'.encode()
69
70 args.output.write(pack('8s', BOOT_MAGIC))
Tao Baoa1494502019-09-17 10:02:40 -070071 args.output.write(pack(
72 '4I',
Steve Muckle9a09efb2019-07-30 12:24:41 -070073 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()))
Steve Muckle769efcf2019-09-30 11:19:48 -070082 pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE)
Steve Muckle9a09efb2019-07-30 12:24:41 -070083
84def write_vendor_boot_header(args):
Steve Muckle5bb77e12020-03-06 11:05:22 -080085 VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112
Steve Muckle9a09efb2019-07-30 12:24:41 -070086 BOOT_MAGIC = 'VNDRBOOT'.encode()
87
88 args.vendor_boot.write(pack('8s', BOOT_MAGIC))
Tao Baoa1494502019-09-17 10:02:40 -070089 args.vendor_boot.write(pack(
90 '5I',
Steve Muckle9a09efb2019-07-30 12:24:41 -070091 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
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700106def write_header(args):
Hridya Valsaraju70275852019-01-24 15:59:15 -0800107 BOOT_IMAGE_HEADER_V1_SIZE = 1648
108 BOOT_IMAGE_HEADER_V2_SIZE = 1660
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700109 BOOT_MAGIC = 'ANDROID!'.encode()
Hridya Valsaraju70275852019-01-24 15:59:15 -0800110
Tao Baoa1494502019-09-17 10:02:40 -0700111 if args.header_version > 3:
Hridya Valsaraju70275852019-01-24 15:59:15 -0800112 raise ValueError('Boot header version %d not supported' % args.header_version)
Steve Muckle9a09efb2019-07-30 12:24:41 -0700113 elif args.header_version == 3:
114 return write_header_v3(args)
Hridya Valsaraju70275852019-01-24 15:59:15 -0800115
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700116 args.output.write(pack('8s', BOOT_MAGIC))
cfig983425f2019-07-19 15:04:39 +0800117 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
Tao Baoa1494502019-09-17 10:02:40 -0700119 args.output.write(pack(
120 '10I',
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700121 filesize(args.kernel), # size in bytes
122 args.base + args.kernel_offset, # physical load addr
123 filesize(args.ramdisk), # size in bytes
cfig983425f2019-07-19 15:04:39 +0800124 final_ramdisk_offset, # physical load addr
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700125 filesize(args.second), # size in bytes
cfig983425f2019-07-19 15:04:39 +0800126 final_second_offset, # physical load addr
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700127 args.base + args.tags_offset, # physical addr for kernel tags
Sami Tolvanenea649102016-03-14 09:08:59 -0700128 args.pagesize, # flash page size we assume
Hridya Valsarajua008dbd2018-03-20 15:26:00 -0700129 args.header_version, # version of bootimage header
Sami Tolvanenea649102016-03-14 09:08:59 -0700130 (args.os_version << 11) | args.os_patch_level)) # os version and patch level
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700131 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)
Hridya Valsarajua008dbd2018-03-20 15:26:00 -0700138
139 if args.header_version > 0:
140 update_sha(sha, args.recovery_dtbo)
Hridya Valsarajue55998a2019-01-22 08:58:27 -0800141 if args.header_version > 1:
142 update_sha(sha, args.dtb)
Hridya Valsarajua008dbd2018-03-20 15:26:00 -0700143
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700144 img_id = pack('32s', sha.digest())
145
146 args.output.write(img_id)
147 args.output.write(pack('1024s', args.cmdline[512:].encode()))
Hridya Valsarajua008dbd2018-03-20 15:26:00 -0700148
149 if args.header_version > 0:
Hridya Valsarajuc1a18a32018-05-31 12:39:58 -0700150 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
Hridya Valsarajua008dbd2018-03-20 15:26:00 -0700155
Hridya Valsaraju70275852019-01-24 15:59:15 -0800156 # 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))
Hridya Valsarajue55998a2019-01-22 08:58:27 -0800161
162 if args.header_version > 1:
Hridya Valsaraju7261bb02019-05-17 16:43:25 -0700163
164 if filesize(args.dtb) == 0:
165 raise ValueError("DTB image must not be empty.")
166
Hridya Valsarajue55998a2019-01-22 08:58:27 -0800167 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
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700169 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:
Tao Baoa1494502019-09-17 10:02:40 -0700183 raise ValueError(
184 'String argument too long: max {0:d}, got {1:d}'.format(self.maxlen, len(values)))
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700185 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
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700195def parse_int(x):
Rom Lemarchand7e233d82015-06-04 09:59:01 -0700196 return int(x, 0)
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700197
Tao Baoa1494502019-09-17 10:02:40 -0700198
Sami Tolvanenea649102016-03-14 09:08:59 -0700199def parse_os_version(x):
200 match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
201 if match:
Sami Tolvanenc4ae0662016-03-29 16:06:37 -0700202 a = int(match.group(1))
Sami Tolvanenea649102016-03-14 09:08:59 -0700203 b = c = 0
204 if match.lastindex >= 2:
Sami Tolvanenc4ae0662016-03-29 16:06:37 -0700205 b = int(match.group(2))
Sami Tolvanenea649102016-03-14 09:08:59 -0700206 if match.lastindex == 3:
Sami Tolvanenc4ae0662016-03-29 16:06:37 -0700207 c = int(match.group(3))
Sami Tolvanenea649102016-03-14 09:08:59 -0700208 # 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
Tao Baoa1494502019-09-17 10:02:40 -0700215
Sami Tolvanenea649102016-03-14 09:08:59 -0700216def parse_os_patch_level(x):
Daniel Mentz1a16af72020-01-03 20:20:19 -0800217 match = re.search(r'^(\d{4})-(\d{2})(?:-(\d{2}))?', x)
Sami Tolvanenea649102016-03-14 09:08:59 -0700218 if match:
Sami Tolvanenc4ae0662016-03-29 16:06:37 -0700219 y = int(match.group(1)) - 2000
220 m = int(match.group(2))
Sami Tolvanenea649102016-03-14 09:08:59 -0700221 # 7 bits allocated for the year, 4 bits for the month
Tao Baoa1494502019-09-17 10:02:40 -0700222 assert 0 <= y < 128
223 assert 0 < m <= 12
Sami Tolvanenea649102016-03-14 09:08:59 -0700224 return (y << 4) | m
225 return 0
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700226
Tao Baoa1494502019-09-17 10:02:40 -0700227
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700228def parse_cmdline():
229 parser = ArgumentParser()
Steve Muckle9a09efb2019-07-30 12:24:41 -0700230 parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'))
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700231 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'))
Hridya Valsarajue55998a2019-01-22 08:58:27 -0800233 parser.add_argument('--dtb', help='path to dtb', type=FileType('rb'))
Chen, ZhiminX1f929de2018-08-30 14:30:32 +0800234 recovery_dtbo_group = parser.add_mutually_exclusive_group()
Tao Baoa1494502019-09-17 10:02:40 -0700235 recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO',
236 type=FileType('rb'))
Chen, ZhiminX1f929de2018-08-30 14:30:32 +0800237 recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO',
Tao Baoa1494502019-09-17 10:02:40 -0700238 type=FileType('rb'), metavar='RECOVERY_ACPIO',
239 dest='recovery_dtbo')
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700240 parser.add_argument('--cmdline', help='extra arguments to be passed on the '
241 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
Tao Baoa1494502019-09-17 10:02:40 -0700242 parser.add_argument('--vendor_cmdline',
243 help='kernel command line arguments contained in vendor boot',
244 default='', action=ValidateStrLenAction, maxlen=2048)
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700245 parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
Rom Lemarchand7e233d82015-06-04 09:59:01 -0700246 parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
Tao Baoa1494502019-09-17 10:02:40 -0700247 parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int,
248 default=0x01000000)
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700249 parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700250 default=0x00f00000)
Hridya Valsarajue55998a2019-01-22 08:58:27 -0800251 parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000)
252
Sami Tolvanenea649102016-03-14 09:08:59 -0700253 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)
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700257 parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700258 parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
259 maxlen=16)
Rom Lemarchand764e74d2015-06-02 19:01:25 -0700260 parser.add_argument('--pagesize', help='page size', type=parse_int,
Tao Baoa1494502019-09-17 10:02:40 -0700261 choices=[2**i for i in range(11, 15)], default=2048)
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700262 parser.add_argument('--id', help='print the image ID on standard output',
263 action='store_true')
Tao Baoa1494502019-09-17 10:02:40 -0700264 parser.add_argument('--header_version', help='boot image header version', type=parse_int,
265 default=0)
Steve Muckle9a09efb2019-07-30 12:24:41 -0700266 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
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700270 return parser.parse_args()
271
272
Steve Muckle769efcf2019-09-30 11:19:48 -0700273def 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)
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700277
Steve Muckle9a09efb2019-07-30 12:24:41 -0700278 if args.header_version > 0 and args.header_version < 3:
Steve Muckle769efcf2019-09-30 11:19:48 -0700279 write_padded_file(args.output, args.recovery_dtbo, pagesize)
Steve Muckle9a09efb2019-07-30 12:24:41 -0700280 if args.header_version == 2:
Steve Muckle769efcf2019-09-30 11:19:48 -0700281 write_padded_file(args.output, args.dtb, pagesize)
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700282
Tao Baoa1494502019-09-17 10:02:40 -0700283
Steve Muckle9a09efb2019-07-30 12:24:41 -0700284def 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
Tao Baoa1494502019-09-17 10:02:40 -0700288
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700289def main():
290 args = parse_cmdline()
Steve Muckle9a09efb2019-07-30 12:24:41 -0700291 if args.vendor_boot is not None:
292 if args.header_version < 3:
Tao Baoa1494502019-09-17 10:02:40 -0700293 raise ValueError('--vendor_boot not compatible with given header version')
Steve Muckle9a09efb2019-07-30 12:24:41 -0700294 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)
Steve Muckle9a09efb2019-07-30 12:24:41 -0700298 if args.output is not None:
299 if args.kernel is None:
300 raise ValueError('kernel must be supplied when creating a boot image')
Steve Muckle769efcf2019-09-30 11:19:48 -0700301 if args.second is not None and args.header_version > 2:
302 raise ValueError('--second not compatible with given header version')
Steve Muckle9a09efb2019-07-30 12:24:41 -0700303 img_id = write_header(args)
Steve Muckle769efcf2019-09-30 11:19:48 -0700304 if args.header_version > 2:
305 write_data(args, BOOT_IMAGE_HEADER_V3_PAGESIZE)
306 else:
307 write_data(args, args.pagesize)
Steve Muckle9a09efb2019-07-30 12:24:41 -0700308 if args.id and img_id is not None:
Tao Baoa1494502019-09-17 10:02:40 -0700309 # Python 2's struct.pack returns a string, but py3 returns bytes.
Steve Muckle9a09efb2019-07-30 12:24:41 -0700310 if isinstance(img_id, str):
Tao Baoa1494502019-09-17 10:02:40 -0700311 img_id = [ord(x) for x in img_id]
Steve Muckle9a09efb2019-07-30 12:24:41 -0700312 print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700313
Tao Baoa1494502019-09-17 10:02:40 -0700314
Rom Lemarchand4ee256c2015-05-19 16:58:40 -0700315if __name__ == '__main__':
316 main()