blob: dc1dd2a7dcff85dd58187bd6409bf22074caf0c9 [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
8from collections import OrderedDict
9import os
10import sys
Simon Glassbf776672020-04-17 18:09:04 -060011from patman import tools
Simon Glassbf7fd502016-11-25 20:15:51 -070012
Simon Glass16287932020-04-17 18:09:03 -060013from binman import cbfs_util
14from binman import elf
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import command
16from patman import tout
Simon Glassbf7fd502016-11-25 20:15:51 -070017
18# List of images we plan to create
19# Make this global so that it can be referenced from tests
20images = OrderedDict()
21
22def _ReadImageDesc(binman_node):
23 """Read the image descriptions from the /binman node
24
25 This normally produces a single Image object called 'image'. But if
26 multiple images are present, they will all be returned.
27
28 Args:
29 binman_node: Node object of the /binman node
30 Returns:
31 OrderedDict of Image objects, each of which describes an image
32 """
33 images = OrderedDict()
34 if 'multiple-images' in binman_node.props:
35 for node in binman_node.subnodes:
36 images[node.name] = Image(node.name, node)
37 else:
38 images['image'] = Image('image', binman_node)
39 return images
40
Simon Glassec3f3782017-05-27 07:38:29 -060041def _FindBinmanNode(dtb):
Simon Glassbf7fd502016-11-25 20:15:51 -070042 """Find the 'binman' node in the device tree
43
44 Args:
Simon Glassec3f3782017-05-27 07:38:29 -060045 dtb: Fdt object to scan
Simon Glassbf7fd502016-11-25 20:15:51 -070046 Returns:
47 Node object of /binman node, or None if not found
48 """
Simon Glassec3f3782017-05-27 07:38:29 -060049 for node in dtb.GetRoot().subnodes:
Simon Glassbf7fd502016-11-25 20:15:51 -070050 if node.name == 'binman':
51 return node
52 return None
53
Simon Glassc55a50f2018-09-14 04:57:19 -060054def WriteEntryDocs(modules, test_missing=None):
55 """Write out documentation for all entries
Simon Glassecab8972018-07-06 10:27:40 -060056
57 Args:
Simon Glassc55a50f2018-09-14 04:57:19 -060058 modules: List of Module objects to get docs for
59 test_missing: Used for testing only, to force an entry's documeentation
60 to show as missing even if it is present. Should be set to None in
61 normal use.
Simon Glassecab8972018-07-06 10:27:40 -060062 """
Simon Glass16287932020-04-17 18:09:03 -060063 from binman.entry import Entry
Simon Glassfd8d1f72018-07-17 13:25:36 -060064 Entry.WriteDocs(modules, test_missing)
65
Simon Glass61f564d2019-07-08 14:25:48 -060066
67def ListEntries(image_fname, entry_paths):
68 """List the entries in an image
69
70 This decodes the supplied image and displays a table of entries from that
71 image, preceded by a header.
72
73 Args:
74 image_fname: Image filename to process
75 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
76 'section/u-boot'])
77 """
78 image = Image.FromFile(image_fname)
79
80 entries, lines, widths = image.GetListEntries(entry_paths)
81
82 num_columns = len(widths)
83 for linenum, line in enumerate(lines):
84 if linenum == 1:
85 # Print header line
86 print('-' * (sum(widths) + num_columns * 2))
87 out = ''
88 for i, item in enumerate(line):
89 width = -widths[i]
90 if item.startswith('>'):
91 width = -width
92 item = item[1:]
93 txt = '%*s ' % (width, item)
94 out += txt
95 print(out.rstrip())
96
Simon Glassf667e452019-07-08 14:25:50 -060097
98def ReadEntry(image_fname, entry_path, decomp=True):
99 """Extract an entry from an image
100
101 This extracts the data from a particular entry in an image
102
103 Args:
104 image_fname: Image filename to process
105 entry_path: Path to entry to extract
106 decomp: True to return uncompressed data, if the data is compress
107 False to return the raw data
108
109 Returns:
110 data extracted from the entry
111 """
Simon Glass8dbb7442019-08-24 07:22:44 -0600112 global Image
113 from image import Image
114
Simon Glassf667e452019-07-08 14:25:50 -0600115 image = Image.FromFile(image_fname)
116 entry = image.FindEntryPath(entry_path)
117 return entry.ReadData(decomp)
118
119
Simon Glass71ce0ba2019-07-08 14:25:52 -0600120def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
121 decomp=True):
122 """Extract the data from one or more entries and write it to files
123
124 Args:
125 image_fname: Image filename to process
126 output_fname: Single output filename to use if extracting one file, None
127 otherwise
128 outdir: Output directory to use (for any number of files), else None
129 entry_paths: List of entry paths to extract
Simon Glass3ad804e2019-07-20 12:24:12 -0600130 decomp: True to decompress the entry data
Simon Glass71ce0ba2019-07-08 14:25:52 -0600131
132 Returns:
133 List of EntryInfo records that were written
134 """
135 image = Image.FromFile(image_fname)
136
137 # Output an entry to a single file, as a special case
138 if output_fname:
139 if not entry_paths:
Simon Glassbb5edc12019-07-20 12:24:14 -0600140 raise ValueError('Must specify an entry path to write with -f')
Simon Glass71ce0ba2019-07-08 14:25:52 -0600141 if len(entry_paths) != 1:
Simon Glassbb5edc12019-07-20 12:24:14 -0600142 raise ValueError('Must specify exactly one entry path to write with -f')
Simon Glass71ce0ba2019-07-08 14:25:52 -0600143 entry = image.FindEntryPath(entry_paths[0])
144 data = entry.ReadData(decomp)
145 tools.WriteFile(output_fname, data)
146 tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
147 return
148
149 # Otherwise we will output to a path given by the entry path of each entry.
150 # This means that entries will appear in subdirectories if they are part of
151 # a sub-section.
152 einfos = image.GetListEntries(entry_paths)[0]
153 tout.Notice('%d entries match and will be written' % len(einfos))
154 for einfo in einfos:
155 entry = einfo.entry
156 data = entry.ReadData(decomp)
157 path = entry.GetPath()[1:]
158 fname = os.path.join(outdir, path)
159
160 # If this entry has children, create a directory for it and put its
161 # data in a file called 'root' in that directory
162 if entry.GetEntries():
163 if not os.path.exists(fname):
164 os.makedirs(fname)
165 fname = os.path.join(fname, 'root')
166 tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
167 tools.WriteFile(fname, data)
168 return einfos
169
170
Simon Glassd7fa4e42019-07-20 12:24:13 -0600171def BeforeReplace(image, allow_resize):
172 """Handle getting an image ready for replacing entries in it
173
174 Args:
175 image: Image to prepare
176 """
177 state.PrepareFromLoadedData(image)
178 image.LoadData()
179
180 # If repacking, drop the old offset/size values except for the original
181 # ones, so we are only left with the constraints.
182 if allow_resize:
183 image.ResetForPack()
184
185
186def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
187 """Handle replacing a single entry an an image
188
189 Args:
190 image: Image to update
191 entry: Entry to write
192 data: Data to replace with
193 do_compress: True to compress the data if needed, False if data is
194 already compressed so should be used as is
195 allow_resize: True to allow entries to change size (this does a re-pack
196 of the entries), False to raise an exception
197 """
198 if not entry.WriteData(data, do_compress):
199 if not image.allow_repack:
200 entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
201 if not allow_resize:
202 entry.Raise('Entry data size does not match, but resize is disabled')
203
204
205def AfterReplace(image, allow_resize, write_map):
206 """Handle write out an image after replacing entries in it
207
208 Args:
209 image: Image to write
210 allow_resize: True to allow entries to change size (this does a re-pack
211 of the entries), False to raise an exception
212 write_map: True to write a map file
213 """
214 tout.Info('Processing image')
215 ProcessImage(image, update_fdt=True, write_map=write_map,
216 get_contents=False, allow_resize=allow_resize)
217
218
219def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
220 write_map=False):
221 BeforeReplace(image, allow_resize)
222 tout.Info('Writing data to %s' % entry.GetPath())
223 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
224 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
225
226
Simon Glass3ad804e2019-07-20 12:24:12 -0600227def WriteEntry(image_fname, entry_path, data, do_compress=True,
228 allow_resize=True, write_map=False):
Simon Glass22a76b72019-07-20 12:24:11 -0600229 """Replace an entry in an image
230
231 This replaces the data in a particular entry in an image. This size of the
232 new data must match the size of the old data unless allow_resize is True.
233
234 Args:
235 image_fname: Image filename to process
236 entry_path: Path to entry to extract
237 data: Data to replace with
Simon Glass3ad804e2019-07-20 12:24:12 -0600238 do_compress: True to compress the data if needed, False if data is
Simon Glass22a76b72019-07-20 12:24:11 -0600239 already compressed so should be used as is
240 allow_resize: True to allow entries to change size (this does a re-pack
241 of the entries), False to raise an exception
Simon Glass3ad804e2019-07-20 12:24:12 -0600242 write_map: True to write a map file
Simon Glass22a76b72019-07-20 12:24:11 -0600243
244 Returns:
245 Image object that was updated
246 """
Simon Glassd7fa4e42019-07-20 12:24:13 -0600247 tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
Simon Glass22a76b72019-07-20 12:24:11 -0600248 image = Image.FromFile(image_fname)
249 entry = image.FindEntryPath(entry_path)
Simon Glassd7fa4e42019-07-20 12:24:13 -0600250 WriteEntryToImage(image, entry, data, do_compress=do_compress,
251 allow_resize=allow_resize, write_map=write_map)
Simon Glass22a76b72019-07-20 12:24:11 -0600252
Simon Glass22a76b72019-07-20 12:24:11 -0600253 return image
254
Simon Glassa6cb9952019-07-20 12:24:15 -0600255
256def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
257 do_compress=True, allow_resize=True, write_map=False):
258 """Replace the data from one or more entries from input files
259
260 Args:
261 image_fname: Image filename to process
262 input_fname: Single input ilename to use if replacing one file, None
263 otherwise
264 indir: Input directory to use (for any number of files), else None
265 entry_paths: List of entry paths to extract
266 do_compress: True if the input data is uncompressed and may need to be
267 compressed if the entry requires it, False if the data is already
268 compressed.
269 write_map: True to write a map file
270
271 Returns:
272 List of EntryInfo records that were written
273 """
274 image = Image.FromFile(image_fname)
275
276 # Replace an entry from a single file, as a special case
277 if input_fname:
278 if not entry_paths:
279 raise ValueError('Must specify an entry path to read with -f')
280 if len(entry_paths) != 1:
281 raise ValueError('Must specify exactly one entry path to write with -f')
282 entry = image.FindEntryPath(entry_paths[0])
283 data = tools.ReadFile(input_fname)
284 tout.Notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
285 WriteEntryToImage(image, entry, data, do_compress=do_compress,
286 allow_resize=allow_resize, write_map=write_map)
287 return
288
289 # Otherwise we will input from a path given by the entry path of each entry.
290 # This means that files must appear in subdirectories if they are part of
291 # a sub-section.
292 einfos = image.GetListEntries(entry_paths)[0]
293 tout.Notice("Replacing %d matching entries in image '%s'" %
294 (len(einfos), image_fname))
295
296 BeforeReplace(image, allow_resize)
297
298 for einfo in einfos:
299 entry = einfo.entry
300 if entry.GetEntries():
301 tout.Info("Skipping section entry '%s'" % entry.GetPath())
302 continue
303
304 path = entry.GetPath()[1:]
305 fname = os.path.join(indir, path)
306
307 if os.path.exists(fname):
308 tout.Notice("Write entry '%s' from file '%s'" %
309 (entry.GetPath(), fname))
310 data = tools.ReadFile(fname)
311 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
312 else:
313 tout.Warning("Skipping entry '%s' from missing file '%s'" %
314 (entry.GetPath(), fname))
315
316 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
317 return image
318
319
Simon Glassa8573c42019-07-20 12:23:27 -0600320def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt):
321 """Prepare the images to be processed and select the device tree
322
323 This function:
324 - reads in the device tree
325 - finds and scans the binman node to create all entries
326 - selects which images to build
327 - Updates the device tress with placeholder properties for offset,
328 image-pos, etc.
329
330 Args:
331 dtb_fname: Filename of the device tree file to use (.dts or .dtb)
332 selected_images: List of images to output, or None for all
333 update_fdt: True to update the FDT wth entry offsets, etc.
334 """
335 # Import these here in case libfdt.py is not available, in which case
336 # the above help option still works.
Simon Glass16287932020-04-17 18:09:03 -0600337 from dtoc import fdt
338 from dtoc import fdt_util
Simon Glassa8573c42019-07-20 12:23:27 -0600339 global images
340
341 # Get the device tree ready by compiling it and copying the compiled
342 # output into a file in our output directly. Then scan it for use
343 # in binman.
344 dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
345 fname = tools.GetOutputFilename('u-boot.dtb.out')
346 tools.WriteFile(fname, tools.ReadFile(dtb_fname))
347 dtb = fdt.FdtScan(fname)
348
349 node = _FindBinmanNode(dtb)
350 if not node:
351 raise ValueError("Device tree '%s' does not have a 'binman' "
352 "node" % dtb_fname)
353
354 images = _ReadImageDesc(node)
355
356 if select_images:
357 skip = []
358 new_images = OrderedDict()
359 for name, image in images.items():
360 if name in select_images:
361 new_images[name] = image
362 else:
363 skip.append(name)
364 images = new_images
365 tout.Notice('Skipping images: %s' % ', '.join(skip))
366
367 state.Prepare(images, dtb)
368
369 # Prepare the device tree by making sure that any missing
370 # properties are added (e.g. 'pos' and 'size'). The values of these
371 # may not be correct yet, but we add placeholders so that the
372 # size of the device tree is correct. Later, in
373 # SetCalculatedProperties() we will insert the correct values
374 # without changing the device-tree size, thus ensuring that our
375 # entry offsets remain the same.
376 for image in images.values():
377 image.ExpandEntries()
378 if update_fdt:
379 image.AddMissingProperties()
380 image.ProcessFdt(dtb)
381
Simon Glass4bdd1152019-07-20 12:23:29 -0600382 for dtb_item in state.GetAllFdts():
Simon Glassa8573c42019-07-20 12:23:27 -0600383 dtb_item.Sync(auto_resize=True)
384 dtb_item.Pack()
385 dtb_item.Flush()
386 return images
387
388
Simon Glass51014aa2019-07-20 12:23:56 -0600389def ProcessImage(image, update_fdt, write_map, get_contents=True,
390 allow_resize=True):
Simon Glassb88e81c2019-07-20 12:23:24 -0600391 """Perform all steps for this image, including checking and # writing it.
392
393 This means that errors found with a later image will be reported after
394 earlier images are already completed and written, but that does not seem
395 important.
396
397 Args:
398 image: Image to process
399 update_fdt: True to update the FDT wth entry offsets, etc.
400 write_map: True to write a map file
Simon Glass10f9d002019-07-20 12:23:50 -0600401 get_contents: True to get the image contents from files, etc., False if
402 the contents is already present
Simon Glass51014aa2019-07-20 12:23:56 -0600403 allow_resize: True to allow entries to change size (this does a re-pack
404 of the entries), False to raise an exception
Simon Glassb88e81c2019-07-20 12:23:24 -0600405 """
Simon Glass10f9d002019-07-20 12:23:50 -0600406 if get_contents:
407 image.GetEntryContents()
Simon Glassb88e81c2019-07-20 12:23:24 -0600408 image.GetEntryOffsets()
409
410 # We need to pack the entries to figure out where everything
411 # should be placed. This sets the offset/size of each entry.
412 # However, after packing we call ProcessEntryContents() which
413 # may result in an entry changing size. In that case we need to
414 # do another pass. Since the device tree often contains the
415 # final offset/size information we try to make space for this in
416 # AddMissingProperties() above. However, if the device is
417 # compressed we cannot know this compressed size in advance,
418 # since changing an offset from 0x100 to 0x104 (for example) can
419 # alter the compressed size of the device tree. So we need a
420 # third pass for this.
Simon Glasseb0f4a42019-07-20 12:24:06 -0600421 passes = 5
Simon Glassb88e81c2019-07-20 12:23:24 -0600422 for pack_pass in range(passes):
423 try:
424 image.PackEntries()
425 image.CheckSize()
426 image.CheckEntries()
427 except Exception as e:
428 if write_map:
429 fname = image.WriteMap()
430 print("Wrote map file '%s' to show errors" % fname)
431 raise
432 image.SetImagePos()
433 if update_fdt:
434 image.SetCalculatedProperties()
Simon Glass4bdd1152019-07-20 12:23:29 -0600435 for dtb_item in state.GetAllFdts():
Simon Glassb88e81c2019-07-20 12:23:24 -0600436 dtb_item.Sync()
Simon Glass51014aa2019-07-20 12:23:56 -0600437 dtb_item.Flush()
Simon Glass261cbe02019-08-24 07:23:12 -0600438 image.WriteSymbols()
Simon Glassb88e81c2019-07-20 12:23:24 -0600439 sizes_ok = image.ProcessEntryContents()
440 if sizes_ok:
441 break
442 image.ResetForPack()
Simon Glassaed6c0b2019-08-24 07:23:13 -0600443 tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
Simon Glassb88e81c2019-07-20 12:23:24 -0600444 if not sizes_ok:
Simon Glass61ec04f2019-07-20 12:23:58 -0600445 image.Raise('Entries changed size after packing (tried %s passes)' %
Simon Glassb88e81c2019-07-20 12:23:24 -0600446 passes)
447
Simon Glassb88e81c2019-07-20 12:23:24 -0600448 image.BuildImage()
449 if write_map:
450 image.WriteMap()
451
452
Simon Glass53cd5d92019-07-08 14:25:29 -0600453def Binman(args):
Simon Glassbf7fd502016-11-25 20:15:51 -0700454 """The main control code for binman
455
456 This assumes that help and test options have already been dealt with. It
457 deals with the core task of building images.
458
459 Args:
Simon Glass53cd5d92019-07-08 14:25:29 -0600460 args: Command line arguments Namespace object
Simon Glassbf7fd502016-11-25 20:15:51 -0700461 """
Simon Glass8dbb7442019-08-24 07:22:44 -0600462 global Image
463 global state
464
Simon Glass53cd5d92019-07-08 14:25:29 -0600465 if args.full_help:
Simon Glassbf7fd502016-11-25 20:15:51 -0700466 pager = os.getenv('PAGER')
467 if not pager:
468 pager = 'more'
469 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
470 'README')
471 command.Run(pager, fname)
472 return 0
473
Simon Glass8dbb7442019-08-24 07:22:44 -0600474 # Put these here so that we can import this module without libfdt
475 from image import Image
Simon Glass16287932020-04-17 18:09:03 -0600476 from binman import state
Simon Glass8dbb7442019-08-24 07:22:44 -0600477
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600478 if args.cmd in ['ls', 'extract', 'replace']:
Simon Glass96b6c502019-07-20 12:23:53 -0600479 try:
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600480 tout.Init(args.verbosity)
Simon Glass96b6c502019-07-20 12:23:53 -0600481 tools.PrepareOutputDir(None)
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600482 if args.cmd == 'ls':
483 ListEntries(args.image, args.paths)
Simon Glass61f564d2019-07-08 14:25:48 -0600484
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600485 if args.cmd == 'extract':
486 ExtractEntries(args.image, args.filename, args.outdir, args.paths,
487 not args.uncompressed)
Simon Glass71ce0ba2019-07-08 14:25:52 -0600488
Simon Glass7bc4f0f2019-09-15 18:10:36 -0600489 if args.cmd == 'replace':
490 ReplaceEntries(args.image, args.filename, args.indir, args.paths,
491 do_compress=not args.compressed,
492 allow_resize=not args.fix_size, write_map=args.map)
493 except:
494 raise
Simon Glassa6cb9952019-07-20 12:24:15 -0600495 finally:
496 tools.FinaliseOutputDir()
497 return 0
498
Simon Glassbf7fd502016-11-25 20:15:51 -0700499 # Try to figure out which device tree contains our image description
Simon Glass53cd5d92019-07-08 14:25:29 -0600500 if args.dt:
501 dtb_fname = args.dt
Simon Glassbf7fd502016-11-25 20:15:51 -0700502 else:
Simon Glass53cd5d92019-07-08 14:25:29 -0600503 board = args.board
Simon Glassbf7fd502016-11-25 20:15:51 -0700504 if not board:
505 raise ValueError('Must provide a board to process (use -b <board>)')
Simon Glass53cd5d92019-07-08 14:25:29 -0600506 board_pathname = os.path.join(args.build_dir, board)
Simon Glassbf7fd502016-11-25 20:15:51 -0700507 dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
Simon Glass53cd5d92019-07-08 14:25:29 -0600508 if not args.indir:
509 args.indir = ['.']
510 args.indir.append(board_pathname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700511
512 try:
Simon Glass53cd5d92019-07-08 14:25:29 -0600513 tout.Init(args.verbosity)
514 elf.debug = args.debug
515 cbfs_util.VERBOSE = args.verbosity > 2
516 state.use_fake_dtb = args.fake_dtb
Simon Glassbf7fd502016-11-25 20:15:51 -0700517 try:
Simon Glass53cd5d92019-07-08 14:25:29 -0600518 tools.SetInputDirs(args.indir)
519 tools.PrepareOutputDir(args.outdir, args.preserve)
520 tools.SetToolPaths(args.toolpath)
521 state.SetEntryArgs(args.entry_arg)
Simon Glassecab8972018-07-06 10:27:40 -0600522
Simon Glassa8573c42019-07-20 12:23:27 -0600523 images = PrepareImagesAndDtbs(dtb_fname, args.image,
524 args.update_fdt)
Simon Glassbf7fd502016-11-25 20:15:51 -0700525 for image in images.values():
Simon Glassb88e81c2019-07-20 12:23:24 -0600526 ProcessImage(image, args.update_fdt, args.map)
Simon Glass2a72cc72018-09-14 04:57:20 -0600527
528 # Write the updated FDTs to our output files
Simon Glass4bdd1152019-07-20 12:23:29 -0600529 for dtb_item in state.GetAllFdts():
Simon Glass2a72cc72018-09-14 04:57:20 -0600530 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
531
Simon Glassbf7fd502016-11-25 20:15:51 -0700532 finally:
533 tools.FinaliseOutputDir()
534 finally:
535 tout.Uninit()
536
537 return 0