blob: 68ad5fc2c0c4d098ddcf430d77be8b21dbe23668 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassbf7fd502016-11-25 20:15:51 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glassbf7fd502016-11-25 20:15:51 -07005# Creates binary images from input files controlled by a description
6#
7
Simon Glass2ca84682019-05-14 15:53:37 -06008from __future__ import print_function
9
Simon Glassbf7fd502016-11-25 20:15:51 -070010from collections import OrderedDict
11import os
12import sys
13import tools
14
Simon Glassac62fba2019-07-08 13:18:53 -060015import cbfs_util
Simon Glassbf7fd502016-11-25 20:15:51 -070016import command
Simon Glass7fe91732017-11-13 18:55:00 -070017import elf
Simon Glassbf7fd502016-11-25 20:15:51 -070018import tout
19
20# List of images we plan to create
21# Make this global so that it can be referenced from tests
22images = OrderedDict()
23
24def _ReadImageDesc(binman_node):
25 """Read the image descriptions from the /binman node
26
27 This normally produces a single Image object called 'image'. But if
28 multiple images are present, they will all be returned.
29
30 Args:
31 binman_node: Node object of the /binman node
32 Returns:
33 OrderedDict of Image objects, each of which describes an image
34 """
35 images = OrderedDict()
36 if 'multiple-images' in binman_node.props:
37 for node in binman_node.subnodes:
38 images[node.name] = Image(node.name, node)
39 else:
40 images['image'] = Image('image', binman_node)
41 return images
42
Simon Glassec3f3782017-05-27 07:38:29 -060043def _FindBinmanNode(dtb):
Simon Glassbf7fd502016-11-25 20:15:51 -070044 """Find the 'binman' node in the device tree
45
46 Args:
Simon Glassec3f3782017-05-27 07:38:29 -060047 dtb: Fdt object to scan
Simon Glassbf7fd502016-11-25 20:15:51 -070048 Returns:
49 Node object of /binman node, or None if not found
50 """
Simon Glassec3f3782017-05-27 07:38:29 -060051 for node in dtb.GetRoot().subnodes:
Simon Glassbf7fd502016-11-25 20:15:51 -070052 if node.name == 'binman':
53 return node
54 return None
55
Simon Glassc55a50f2018-09-14 04:57:19 -060056def WriteEntryDocs(modules, test_missing=None):
57 """Write out documentation for all entries
Simon Glassecab8972018-07-06 10:27:40 -060058
59 Args:
Simon Glassc55a50f2018-09-14 04:57:19 -060060 modules: List of Module objects to get docs for
61 test_missing: Used for testing only, to force an entry's documeentation
62 to show as missing even if it is present. Should be set to None in
63 normal use.
Simon Glassecab8972018-07-06 10:27:40 -060064 """
Simon Glassfd8d1f72018-07-17 13:25:36 -060065 from entry import Entry
66 Entry.WriteDocs(modules, test_missing)
67
Simon Glass61f564d2019-07-08 14:25:48 -060068
69def ListEntries(image_fname, entry_paths):
70 """List the entries in an image
71
72 This decodes the supplied image and displays a table of entries from that
73 image, preceded by a header.
74
75 Args:
76 image_fname: Image filename to process
77 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
78 'section/u-boot'])
79 """
80 image = Image.FromFile(image_fname)
81
82 entries, lines, widths = image.GetListEntries(entry_paths)
83
84 num_columns = len(widths)
85 for linenum, line in enumerate(lines):
86 if linenum == 1:
87 # Print header line
88 print('-' * (sum(widths) + num_columns * 2))
89 out = ''
90 for i, item in enumerate(line):
91 width = -widths[i]
92 if item.startswith('>'):
93 width = -width
94 item = item[1:]
95 txt = '%*s ' % (width, item)
96 out += txt
97 print(out.rstrip())
98
Simon Glassf667e452019-07-08 14:25:50 -060099
100def ReadEntry(image_fname, entry_path, decomp=True):
101 """Extract an entry from an image
102
103 This extracts the data from a particular entry in an image
104
105 Args:
106 image_fname: Image filename to process
107 entry_path: Path to entry to extract
108 decomp: True to return uncompressed data, if the data is compress
109 False to return the raw data
110
111 Returns:
112 data extracted from the entry
113 """
Simon Glass8dbb7442019-08-24 07:22:44 -0600114 global Image
115 from image import Image
116
Simon Glassf667e452019-07-08 14:25:50 -0600117 image = Image.FromFile(image_fname)
118 entry = image.FindEntryPath(entry_path)
119 return entry.ReadData(decomp)
120
121
Simon Glass71ce0ba2019-07-08 14:25:52 -0600122def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
123 decomp=True):
124 """Extract the data from one or more entries and write it to files
125
126 Args:
127 image_fname: Image filename to process
128 output_fname: Single output filename to use if extracting one file, None
129 otherwise
130 outdir: Output directory to use (for any number of files), else None
131 entry_paths: List of entry paths to extract
Simon Glass3ad804e2019-07-20 12:24:12 -0600132 decomp: True to decompress the entry data
Simon Glass71ce0ba2019-07-08 14:25:52 -0600133
134 Returns:
135 List of EntryInfo records that were written
136 """
137 image = Image.FromFile(image_fname)
138
139 # Output an entry to a single file, as a special case
140 if output_fname:
141 if not entry_paths:
Simon Glassbb5edc12019-07-20 12:24:14 -0600142 raise ValueError('Must specify an entry path to write with -f')
Simon Glass71ce0ba2019-07-08 14:25:52 -0600143 if len(entry_paths) != 1:
Simon Glassbb5edc12019-07-20 12:24:14 -0600144 raise ValueError('Must specify exactly one entry path to write with -f')
Simon Glass71ce0ba2019-07-08 14:25:52 -0600145 entry = image.FindEntryPath(entry_paths[0])
146 data = entry.ReadData(decomp)
147 tools.WriteFile(output_fname, data)
148 tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
149 return
150
151 # Otherwise we will output to a path given by the entry path of each entry.
152 # This means that entries will appear in subdirectories if they are part of
153 # a sub-section.
154 einfos = image.GetListEntries(entry_paths)[0]
155 tout.Notice('%d entries match and will be written' % len(einfos))
156 for einfo in einfos:
157 entry = einfo.entry
158 data = entry.ReadData(decomp)
159 path = entry.GetPath()[1:]
160 fname = os.path.join(outdir, path)
161
162 # If this entry has children, create a directory for it and put its
163 # data in a file called 'root' in that directory
164 if entry.GetEntries():
165 if not os.path.exists(fname):
166 os.makedirs(fname)
167 fname = os.path.join(fname, 'root')
168 tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
169 tools.WriteFile(fname, data)
170 return einfos
171
172
Simon Glassd7fa4e42019-07-20 12:24:13 -0600173def BeforeReplace(image, allow_resize):
174 """Handle getting an image ready for replacing entries in it
175
176 Args:
177 image: Image to prepare
178 """
179 state.PrepareFromLoadedData(image)
180 image.LoadData()
181
182 # If repacking, drop the old offset/size values except for the original
183 # ones, so we are only left with the constraints.
184 if allow_resize:
185 image.ResetForPack()
186
187
188def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
189 """Handle replacing a single entry an an image
190
191 Args:
192 image: Image to update
193 entry: Entry to write
194 data: Data to replace with
195 do_compress: True to compress the data if needed, False if data is
196 already compressed so should be used as is
197 allow_resize: True to allow entries to change size (this does a re-pack
198 of the entries), False to raise an exception
199 """
200 if not entry.WriteData(data, do_compress):
201 if not image.allow_repack:
202 entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
203 if not allow_resize:
204 entry.Raise('Entry data size does not match, but resize is disabled')
205
206
207def AfterReplace(image, allow_resize, write_map):
208 """Handle write out an image after replacing entries in it
209
210 Args:
211 image: Image to write
212 allow_resize: True to allow entries to change size (this does a re-pack
213 of the entries), False to raise an exception
214 write_map: True to write a map file
215 """
216 tout.Info('Processing image')
217 ProcessImage(image, update_fdt=True, write_map=write_map,
218 get_contents=False, allow_resize=allow_resize)
219
220
221def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
222 write_map=False):
223 BeforeReplace(image, allow_resize)
224 tout.Info('Writing data to %s' % entry.GetPath())
225 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
226 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
227
228
Simon Glass3ad804e2019-07-20 12:24:12 -0600229def WriteEntry(image_fname, entry_path, data, do_compress=True,
230 allow_resize=True, write_map=False):
Simon Glass22a76b72019-07-20 12:24:11 -0600231 """Replace an entry in an image
232
233 This replaces the data in a particular entry in an image. This size of the
234 new data must match the size of the old data unless allow_resize is True.
235
236 Args:
237 image_fname: Image filename to process
238 entry_path: Path to entry to extract
239 data: Data to replace with
Simon Glass3ad804e2019-07-20 12:24:12 -0600240 do_compress: True to compress the data if needed, False if data is
Simon Glass22a76b72019-07-20 12:24:11 -0600241 already compressed so should be used as is
242 allow_resize: True to allow entries to change size (this does a re-pack
243 of the entries), False to raise an exception
Simon Glass3ad804e2019-07-20 12:24:12 -0600244 write_map: True to write a map file
Simon Glass22a76b72019-07-20 12:24:11 -0600245
246 Returns:
247 Image object that was updated
248 """
Simon Glassd7fa4e42019-07-20 12:24:13 -0600249 tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
Simon Glass22a76b72019-07-20 12:24:11 -0600250 image = Image.FromFile(image_fname)
251 entry = image.FindEntryPath(entry_path)
Simon Glassd7fa4e42019-07-20 12:24:13 -0600252 WriteEntryToImage(image, entry, data, do_compress=do_compress,
253 allow_resize=allow_resize, write_map=write_map)
Simon Glass22a76b72019-07-20 12:24:11 -0600254
Simon Glass22a76b72019-07-20 12:24:11 -0600255 return image
256
Simon Glassa6cb9952019-07-20 12:24:15 -0600257
258def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
259 do_compress=True, allow_resize=True, write_map=False):
260 """Replace the data from one or more entries from input files
261
262 Args:
263 image_fname: Image filename to process
264 input_fname: Single input ilename to use if replacing one file, None
265 otherwise
266 indir: Input directory to use (for any number of files), else None
267 entry_paths: List of entry paths to extract
268 do_compress: True if the input data is uncompressed and may need to be
269 compressed if the entry requires it, False if the data is already
270 compressed.
271 write_map: True to write a map file
272
273 Returns:
274 List of EntryInfo records that were written
275 """
276 image = Image.FromFile(image_fname)
277
278 # Replace an entry from a single file, as a special case
279 if input_fname:
280 if not entry_paths:
281 raise ValueError('Must specify an entry path to read with -f')
282 if len(entry_paths) != 1:
283 raise ValueError('Must specify exactly one entry path to write with -f')
284 entry = image.FindEntryPath(entry_paths[0])
285 data = tools.ReadFile(input_fname)
286 tout.Notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
287 WriteEntryToImage(image, entry, data, do_compress=do_compress,
288 allow_resize=allow_resize, write_map=write_map)
289 return
290
291 # Otherwise we will input from a path given by the entry path of each entry.
292 # This means that files must appear in subdirectories if they are part of
293 # a sub-section.
294 einfos = image.GetListEntries(entry_paths)[0]
295 tout.Notice("Replacing %d matching entries in image '%s'" %
296 (len(einfos), image_fname))
297
298 BeforeReplace(image, allow_resize)
299
300 for einfo in einfos:
301 entry = einfo.entry
302 if entry.GetEntries():
303 tout.Info("Skipping section entry '%s'" % entry.GetPath())
304 continue
305
306 path = entry.GetPath()[1:]
307 fname = os.path.join(indir, path)
308
309 if os.path.exists(fname):
310 tout.Notice("Write entry '%s' from file '%s'" %
311 (entry.GetPath(), fname))
312 data = tools.ReadFile(fname)
313 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
314 else:
315 tout.Warning("Skipping entry '%s' from missing file '%s'" %
316 (entry.GetPath(), fname))
317
318 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
319 return image
320
321
Simon Glassa8573c42019-07-20 12:23:27 -0600322def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt):
323 """Prepare the images to be processed and select the device tree
324
325 This function:
326 - reads in the device tree
327 - finds and scans the binman node to create all entries
328 - selects which images to build
329 - Updates the device tress with placeholder properties for offset,
330 image-pos, etc.
331
332 Args:
333 dtb_fname: Filename of the device tree file to use (.dts or .dtb)
334 selected_images: List of images to output, or None for all
335 update_fdt: True to update the FDT wth entry offsets, etc.
336 """
337 # Import these here in case libfdt.py is not available, in which case
338 # the above help option still works.
339 import fdt
340 import fdt_util
341 global images
342
343 # Get the device tree ready by compiling it and copying the compiled
344 # output into a file in our output directly. Then scan it for use
345 # in binman.
346 dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
347 fname = tools.GetOutputFilename('u-boot.dtb.out')
348 tools.WriteFile(fname, tools.ReadFile(dtb_fname))
349 dtb = fdt.FdtScan(fname)
350
351 node = _FindBinmanNode(dtb)
352 if not node:
353 raise ValueError("Device tree '%s' does not have a 'binman' "
354 "node" % dtb_fname)
355
356 images = _ReadImageDesc(node)
357
358 if select_images:
359 skip = []
360 new_images = OrderedDict()
361 for name, image in images.items():
362 if name in select_images:
363 new_images[name] = image
364 else:
365 skip.append(name)
366 images = new_images
367 tout.Notice('Skipping images: %s' % ', '.join(skip))
368
369 state.Prepare(images, dtb)
370
371 # Prepare the device tree by making sure that any missing
372 # properties are added (e.g. 'pos' and 'size'). The values of these
373 # may not be correct yet, but we add placeholders so that the
374 # size of the device tree is correct. Later, in
375 # SetCalculatedProperties() we will insert the correct values
376 # without changing the device-tree size, thus ensuring that our
377 # entry offsets remain the same.
378 for image in images.values():
379 image.ExpandEntries()
380 if update_fdt:
381 image.AddMissingProperties()
382 image.ProcessFdt(dtb)
383
Simon Glass4bdd1152019-07-20 12:23:29 -0600384 for dtb_item in state.GetAllFdts():
Simon Glassa8573c42019-07-20 12:23:27 -0600385 dtb_item.Sync(auto_resize=True)
386 dtb_item.Pack()
387 dtb_item.Flush()
388 return images
389
390
Simon Glass51014aa2019-07-20 12:23:56 -0600391def ProcessImage(image, update_fdt, write_map, get_contents=True,
392 allow_resize=True):
Simon Glassb88e81c2019-07-20 12:23:24 -0600393 """Perform all steps for this image, including checking and # writing it.
394
395 This means that errors found with a later image will be reported after
396 earlier images are already completed and written, but that does not seem
397 important.
398
399 Args:
400 image: Image to process
401 update_fdt: True to update the FDT wth entry offsets, etc.
402 write_map: True to write a map file
Simon Glass10f9d002019-07-20 12:23:50 -0600403 get_contents: True to get the image contents from files, etc., False if
404 the contents is already present
Simon Glass51014aa2019-07-20 12:23:56 -0600405 allow_resize: True to allow entries to change size (this does a re-pack
406 of the entries), False to raise an exception
Simon Glassb88e81c2019-07-20 12:23:24 -0600407 """
Simon Glass10f9d002019-07-20 12:23:50 -0600408 if get_contents:
409 image.GetEntryContents()
Simon Glassb88e81c2019-07-20 12:23:24 -0600410 image.GetEntryOffsets()
411
412 # We need to pack the entries to figure out where everything
413 # should be placed. This sets the offset/size of each entry.
414 # However, after packing we call ProcessEntryContents() which
415 # may result in an entry changing size. In that case we need to
416 # do another pass. Since the device tree often contains the
417 # final offset/size information we try to make space for this in
418 # AddMissingProperties() above. However, if the device is
419 # compressed we cannot know this compressed size in advance,
420 # since changing an offset from 0x100 to 0x104 (for example) can
421 # alter the compressed size of the device tree. So we need a
422 # third pass for this.
Simon Glasseb0f4a42019-07-20 12:24:06 -0600423 passes = 5
Simon Glassb88e81c2019-07-20 12:23:24 -0600424 for pack_pass in range(passes):
425 try:
426 image.PackEntries()
427 image.CheckSize()
428 image.CheckEntries()
429 except Exception as e:
430 if write_map:
431 fname = image.WriteMap()
432 print("Wrote map file '%s' to show errors" % fname)
433 raise
434 image.SetImagePos()
435 if update_fdt:
436 image.SetCalculatedProperties()
Simon Glass4bdd1152019-07-20 12:23:29 -0600437 for dtb_item in state.GetAllFdts():
Simon Glassb88e81c2019-07-20 12:23:24 -0600438 dtb_item.Sync()
Simon Glass51014aa2019-07-20 12:23:56 -0600439 dtb_item.Flush()
Simon Glass261cbe02019-08-24 07:23:12 -0600440 image.WriteSymbols()
Simon Glassb88e81c2019-07-20 12:23:24 -0600441 sizes_ok = image.ProcessEntryContents()
442 if sizes_ok:
443 break
444 image.ResetForPack()
Simon Glassaed6c0b2019-08-24 07:23:13 -0600445 tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
Simon Glassb88e81c2019-07-20 12:23:24 -0600446 if not sizes_ok:
Simon Glass61ec04f2019-07-20 12:23:58 -0600447 image.Raise('Entries changed size after packing (tried %s passes)' %
Simon Glassb88e81c2019-07-20 12:23:24 -0600448 passes)
449
Simon Glassb88e81c2019-07-20 12:23:24 -0600450 image.BuildImage()
451 if write_map:
452 image.WriteMap()
453
454
Simon Glass53cd5d92019-07-08 14:25:29 -0600455def Binman(args):
Simon Glassbf7fd502016-11-25 20:15:51 -0700456 """The main control code for binman
457
458 This assumes that help and test options have already been dealt with. It
459 deals with the core task of building images.
460
461 Args:
Simon Glass53cd5d92019-07-08 14:25:29 -0600462 args: Command line arguments Namespace object
Simon Glassbf7fd502016-11-25 20:15:51 -0700463 """
Simon Glass8dbb7442019-08-24 07:22:44 -0600464 global Image
465 global state
466
Simon Glass53cd5d92019-07-08 14:25:29 -0600467 if args.full_help:
Simon Glassbf7fd502016-11-25 20:15:51 -0700468 pager = os.getenv('PAGER')
469 if not pager:
470 pager = 'more'
471 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
472 'README')
473 command.Run(pager, fname)
474 return 0
475
Simon Glass8dbb7442019-08-24 07:22:44 -0600476 # Put these here so that we can import this module without libfdt
477 from image import Image
478 import state
479
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600480 if args.cmd in ['ls', 'extract', 'replace']:
Simon Glass96b6c502019-07-20 12:23:53 -0600481 try:
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600482 tout.Init(args.verbosity)
Simon Glass96b6c502019-07-20 12:23:53 -0600483 tools.PrepareOutputDir(None)
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600484 if args.cmd == 'ls':
485 ListEntries(args.image, args.paths)
Simon Glass61f564d2019-07-08 14:25:48 -0600486
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600487 if args.cmd == 'extract':
488 ExtractEntries(args.image, args.filename, args.outdir, args.paths,
489 not args.uncompressed)
Simon Glass71ce0ba2019-07-08 14:25:52 -0600490
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600491 if args.cmd == 'replace':
492 ReplaceEntries(args.image, args.filename, args.indir, args.paths,
493 do_compress=not args.compressed,
494 allow_resize=not args.fix_size, write_map=args.map)
495 except:
496 raise
Simon Glassa6cb9952019-07-20 12:24:15 -0600497 finally:
498 tools.FinaliseOutputDir()
499 return 0
500
Simon Glassbf7fd502016-11-25 20:15:51 -0700501 # Try to figure out which device tree contains our image description
Simon Glass53cd5d92019-07-08 14:25:29 -0600502 if args.dt:
503 dtb_fname = args.dt
Simon Glassbf7fd502016-11-25 20:15:51 -0700504 else:
Simon Glass53cd5d92019-07-08 14:25:29 -0600505 board = args.board
Simon Glassbf7fd502016-11-25 20:15:51 -0700506 if not board:
507 raise ValueError('Must provide a board to process (use -b <board>)')
Simon Glass53cd5d92019-07-08 14:25:29 -0600508 board_pathname = os.path.join(args.build_dir, board)
Simon Glassbf7fd502016-11-25 20:15:51 -0700509 dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
Simon Glass53cd5d92019-07-08 14:25:29 -0600510 if not args.indir:
511 args.indir = ['.']
512 args.indir.append(board_pathname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700513
514 try:
Simon Glass53cd5d92019-07-08 14:25:29 -0600515 tout.Init(args.verbosity)
516 elf.debug = args.debug
517 cbfs_util.VERBOSE = args.verbosity > 2
518 state.use_fake_dtb = args.fake_dtb
Simon Glassbf7fd502016-11-25 20:15:51 -0700519 try:
Simon Glass53cd5d92019-07-08 14:25:29 -0600520 tools.SetInputDirs(args.indir)
521 tools.PrepareOutputDir(args.outdir, args.preserve)
522 tools.SetToolPaths(args.toolpath)
523 state.SetEntryArgs(args.entry_arg)
Simon Glassecab8972018-07-06 10:27:40 -0600524
Simon Glassa8573c42019-07-20 12:23:27 -0600525 images = PrepareImagesAndDtbs(dtb_fname, args.image,
526 args.update_fdt)
Simon Glassbf7fd502016-11-25 20:15:51 -0700527 for image in images.values():
Simon Glassb88e81c2019-07-20 12:23:24 -0600528 ProcessImage(image, args.update_fdt, args.map)
Simon Glass2a72cc72018-09-14 04:57:20 -0600529
530 # Write the updated FDTs to our output files
Simon Glass4bdd1152019-07-20 12:23:29 -0600531 for dtb_item in state.GetAllFdts():
Simon Glass2a72cc72018-09-14 04:57:20 -0600532 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
533
Simon Glassbf7fd502016-11-25 20:15:51 -0700534 finally:
535 tools.FinaliseOutputDir()
536 finally:
537 tout.Uninit()
538
539 return 0