]> Git Repo - J-u-boot.git/blame - tools/binman/control.py
binman: Put compressed data into separate files
[J-u-boot.git] / tools / binman / control.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
bf7fd50b
SG
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <[email protected]>
4#
bf7fd50b
SG
5# Creates binary images from input files controlled by a description
6#
7
8from collections import OrderedDict
87d43329 9import glob
bf7fd50b 10import os
9fbfaba0 11import pkg_resources
b238143d 12import re
9fbfaba0 13
bf7fd50b 14import sys
bf776679 15from patman import tools
bf7fd50b 16
16287933
SG
17from binman import cbfs_util
18from binman import elf
bf776679
SG
19from patman import command
20from patman import tout
bf7fd50b
SG
21
22# List of images we plan to create
23# Make this global so that it can be referenced from tests
24images = OrderedDict()
25
b238143d
SG
26# Help text for each type of missing blob, dict:
27# key: Value of the entry's 'missing-msg' or entry name
28# value: Text for the help
29missing_blob_help = {}
30
0b6023ee 31def _ReadImageDesc(binman_node, use_expanded):
bf7fd50b
SG
32 """Read the image descriptions from the /binman node
33
34 This normally produces a single Image object called 'image'. But if
35 multiple images are present, they will all be returned.
36
37 Args:
38 binman_node: Node object of the /binman node
0b6023ee 39 use_expanded: True if the FDT will be updated with the entry information
bf7fd50b
SG
40 Returns:
41 OrderedDict of Image objects, each of which describes an image
42 """
43 images = OrderedDict()
44 if 'multiple-images' in binman_node.props:
45 for node in binman_node.subnodes:
0b6023ee
SG
46 images[node.name] = Image(node.name, node,
47 use_expanded=use_expanded)
bf7fd50b 48 else:
0b6023ee 49 images['image'] = Image('image', binman_node, use_expanded=use_expanded)
bf7fd50b
SG
50 return images
51
ec3f378a 52def _FindBinmanNode(dtb):
bf7fd50b
SG
53 """Find the 'binman' node in the device tree
54
55 Args:
ec3f378a 56 dtb: Fdt object to scan
bf7fd50b
SG
57 Returns:
58 Node object of /binman node, or None if not found
59 """
ec3f378a 60 for node in dtb.GetRoot().subnodes:
bf7fd50b
SG
61 if node.name == 'binman':
62 return node
63 return None
64
b238143d
SG
65def _ReadMissingBlobHelp():
66 """Read the missing-blob-help file
67
68 This file containins help messages explaining what to do when external blobs
69 are missing.
70
71 Returns:
72 Dict:
73 key: Message tag (str)
74 value: Message text (str)
75 """
76
77 def _FinishTag(tag, msg, result):
78 if tag:
79 result[tag] = msg.rstrip()
80 tag = None
81 msg = ''
82 return tag, msg
83
84 my_data = pkg_resources.resource_string(__name__, 'missing-blob-help')
85 re_tag = re.compile('^([-a-z0-9]+):$')
86 result = {}
87 tag = None
88 msg = ''
89 for line in my_data.decode('utf-8').splitlines():
90 if not line.startswith('#'):
91 m_tag = re_tag.match(line)
92 if m_tag:
93 _, msg = _FinishTag(tag, msg, result)
94 tag = m_tag.group(1)
95 elif tag:
96 msg += line + '\n'
97 _FinishTag(tag, msg, result)
98 return result
99
100def _ShowBlobHelp(path, text):
101 tout.Warning('\n%s:' % path)
102 for line in text.splitlines():
103 tout.Warning(' %s' % line)
104
105def _ShowHelpForMissingBlobs(missing_list):
106 """Show help for each missing blob to help the user take action
107
108 Args:
109 missing_list: List of Entry objects to show help for
110 """
111 global missing_blob_help
112
113 if not missing_blob_help:
114 missing_blob_help = _ReadMissingBlobHelp()
115
116 for entry in missing_list:
117 tags = entry.GetHelpTags()
118
119 # Show the first match help message
120 for tag in tags:
121 if tag in missing_blob_help:
122 _ShowBlobHelp(entry._node.path, missing_blob_help[tag])
123 break
124
87d43329
SG
125def GetEntryModules(include_testing=True):
126 """Get a set of entry class implementations
127
128 Returns:
129 Set of paths to entry class filenames
130 """
9fbfaba0
SG
131 glob_list = pkg_resources.resource_listdir(__name__, 'etype')
132 glob_list = [fname for fname in glob_list if fname.endswith('.py')]
87d43329
SG
133 return set([os.path.splitext(os.path.basename(item))[0]
134 for item in glob_list
135 if include_testing or '_testing' not in item])
136
c55a50f5
SG
137def WriteEntryDocs(modules, test_missing=None):
138 """Write out documentation for all entries
ecab8973
SG
139
140 Args:
c55a50f5
SG
141 modules: List of Module objects to get docs for
142 test_missing: Used for testing only, to force an entry's documeentation
143 to show as missing even if it is present. Should be set to None in
144 normal use.
ecab8973 145 """
16287933 146 from binman.entry import Entry
fd8d1f79
SG
147 Entry.WriteDocs(modules, test_missing)
148
61f564d1
SG
149
150def ListEntries(image_fname, entry_paths):
151 """List the entries in an image
152
153 This decodes the supplied image and displays a table of entries from that
154 image, preceded by a header.
155
156 Args:
157 image_fname: Image filename to process
158 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
159 'section/u-boot'])
160 """
161 image = Image.FromFile(image_fname)
162
163 entries, lines, widths = image.GetListEntries(entry_paths)
164
165 num_columns = len(widths)
166 for linenum, line in enumerate(lines):
167 if linenum == 1:
168 # Print header line
169 print('-' * (sum(widths) + num_columns * 2))
170 out = ''
171 for i, item in enumerate(line):
172 width = -widths[i]
173 if item.startswith('>'):
174 width = -width
175 item = item[1:]
176 txt = '%*s ' % (width, item)
177 out += txt
178 print(out.rstrip())
179
f667e45b
SG
180
181def ReadEntry(image_fname, entry_path, decomp=True):
182 """Extract an entry from an image
183
184 This extracts the data from a particular entry in an image
185
186 Args:
187 image_fname: Image filename to process
188 entry_path: Path to entry to extract
189 decomp: True to return uncompressed data, if the data is compress
190 False to return the raw data
191
192 Returns:
193 data extracted from the entry
194 """
8dbb7444 195 global Image
07237988 196 from binman.image import Image
8dbb7444 197
f667e45b
SG
198 image = Image.FromFile(image_fname)
199 entry = image.FindEntryPath(entry_path)
200 return entry.ReadData(decomp)
201
202
71ce0ba2
SG
203def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
204 decomp=True):
205 """Extract the data from one or more entries and write it to files
206
207 Args:
208 image_fname: Image filename to process
209 output_fname: Single output filename to use if extracting one file, None
210 otherwise
211 outdir: Output directory to use (for any number of files), else None
212 entry_paths: List of entry paths to extract
3ad804e6 213 decomp: True to decompress the entry data
71ce0ba2
SG
214
215 Returns:
216 List of EntryInfo records that were written
217 """
218 image = Image.FromFile(image_fname)
219
220 # Output an entry to a single file, as a special case
221 if output_fname:
222 if not entry_paths:
bb5edc1d 223 raise ValueError('Must specify an entry path to write with -f')
71ce0ba2 224 if len(entry_paths) != 1:
bb5edc1d 225 raise ValueError('Must specify exactly one entry path to write with -f')
71ce0ba2
SG
226 entry = image.FindEntryPath(entry_paths[0])
227 data = entry.ReadData(decomp)
228 tools.WriteFile(output_fname, data)
229 tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
230 return
231
232 # Otherwise we will output to a path given by the entry path of each entry.
233 # This means that entries will appear in subdirectories if they are part of
234 # a sub-section.
235 einfos = image.GetListEntries(entry_paths)[0]
236 tout.Notice('%d entries match and will be written' % len(einfos))
237 for einfo in einfos:
238 entry = einfo.entry
239 data = entry.ReadData(decomp)
240 path = entry.GetPath()[1:]
241 fname = os.path.join(outdir, path)
242
243 # If this entry has children, create a directory for it and put its
244 # data in a file called 'root' in that directory
245 if entry.GetEntries():
862ddf91 246 if fname and not os.path.exists(fname):
71ce0ba2
SG
247 os.makedirs(fname)
248 fname = os.path.join(fname, 'root')
5b378e4d
SG
249 tout.Notice("Write entry '%s' size %x to '%s'" %
250 (entry.GetPath(), len(data), fname))
71ce0ba2
SG
251 tools.WriteFile(fname, data)
252 return einfos
253
254
d7fa4e4b
SG
255def BeforeReplace(image, allow_resize):
256 """Handle getting an image ready for replacing entries in it
257
258 Args:
259 image: Image to prepare
260 """
261 state.PrepareFromLoadedData(image)
262 image.LoadData()
263
264 # If repacking, drop the old offset/size values except for the original
265 # ones, so we are only left with the constraints.
266 if allow_resize:
267 image.ResetForPack()
268
269
270def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
271 """Handle replacing a single entry an an image
272
273 Args:
274 image: Image to update
275 entry: Entry to write
276 data: Data to replace with
277 do_compress: True to compress the data if needed, False if data is
278 already compressed so should be used as is
279 allow_resize: True to allow entries to change size (this does a re-pack
280 of the entries), False to raise an exception
281 """
282 if not entry.WriteData(data, do_compress):
283 if not image.allow_repack:
284 entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
285 if not allow_resize:
286 entry.Raise('Entry data size does not match, but resize is disabled')
287
288
289def AfterReplace(image, allow_resize, write_map):
290 """Handle write out an image after replacing entries in it
291
292 Args:
293 image: Image to write
294 allow_resize: True to allow entries to change size (this does a re-pack
295 of the entries), False to raise an exception
296 write_map: True to write a map file
297 """
298 tout.Info('Processing image')
299 ProcessImage(image, update_fdt=True, write_map=write_map,
300 get_contents=False, allow_resize=allow_resize)
301
302
303def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
304 write_map=False):
305 BeforeReplace(image, allow_resize)
306 tout.Info('Writing data to %s' % entry.GetPath())
307 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
308 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
309
310
3ad804e6
SG
311def WriteEntry(image_fname, entry_path, data, do_compress=True,
312 allow_resize=True, write_map=False):
22a76b74
SG
313 """Replace an entry in an image
314
315 This replaces the data in a particular entry in an image. This size of the
316 new data must match the size of the old data unless allow_resize is True.
317
318 Args:
319 image_fname: Image filename to process
320 entry_path: Path to entry to extract
321 data: Data to replace with
3ad804e6 322 do_compress: True to compress the data if needed, False if data is
22a76b74
SG
323 already compressed so should be used as is
324 allow_resize: True to allow entries to change size (this does a re-pack
325 of the entries), False to raise an exception
3ad804e6 326 write_map: True to write a map file
22a76b74
SG
327
328 Returns:
329 Image object that was updated
330 """
d7fa4e4b 331 tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
22a76b74
SG
332 image = Image.FromFile(image_fname)
333 entry = image.FindEntryPath(entry_path)
d7fa4e4b
SG
334 WriteEntryToImage(image, entry, data, do_compress=do_compress,
335 allow_resize=allow_resize, write_map=write_map)
22a76b74 336
22a76b74
SG
337 return image
338
a6cb9950
SG
339
340def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
341 do_compress=True, allow_resize=True, write_map=False):
342 """Replace the data from one or more entries from input files
343
344 Args:
345 image_fname: Image filename to process
346 input_fname: Single input ilename to use if replacing one file, None
347 otherwise
348 indir: Input directory to use (for any number of files), else None
349 entry_paths: List of entry paths to extract
350 do_compress: True if the input data is uncompressed and may need to be
351 compressed if the entry requires it, False if the data is already
352 compressed.
353 write_map: True to write a map file
354
355 Returns:
356 List of EntryInfo records that were written
357 """
358 image = Image.FromFile(image_fname)
359
360 # Replace an entry from a single file, as a special case
361 if input_fname:
362 if not entry_paths:
363 raise ValueError('Must specify an entry path to read with -f')
364 if len(entry_paths) != 1:
365 raise ValueError('Must specify exactly one entry path to write with -f')
366 entry = image.FindEntryPath(entry_paths[0])
367 data = tools.ReadFile(input_fname)
368 tout.Notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
369 WriteEntryToImage(image, entry, data, do_compress=do_compress,
370 allow_resize=allow_resize, write_map=write_map)
371 return
372
373 # Otherwise we will input from a path given by the entry path of each entry.
374 # This means that files must appear in subdirectories if they are part of
375 # a sub-section.
376 einfos = image.GetListEntries(entry_paths)[0]
377 tout.Notice("Replacing %d matching entries in image '%s'" %
378 (len(einfos), image_fname))
379
380 BeforeReplace(image, allow_resize)
381
382 for einfo in einfos:
383 entry = einfo.entry
384 if entry.GetEntries():
385 tout.Info("Skipping section entry '%s'" % entry.GetPath())
386 continue
387
388 path = entry.GetPath()[1:]
389 fname = os.path.join(indir, path)
390
391 if os.path.exists(fname):
392 tout.Notice("Write entry '%s' from file '%s'" %
393 (entry.GetPath(), fname))
394 data = tools.ReadFile(fname)
395 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
396 else:
397 tout.Warning("Skipping entry '%s' from missing file '%s'" %
398 (entry.GetPath(), fname))
399
400 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
401 return image
402
403
0b6023ee 404def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
a8573c4c
SG
405 """Prepare the images to be processed and select the device tree
406
407 This function:
408 - reads in the device tree
409 - finds and scans the binman node to create all entries
410 - selects which images to build
411 - Updates the device tress with placeholder properties for offset,
412 image-pos, etc.
413
414 Args:
415 dtb_fname: Filename of the device tree file to use (.dts or .dtb)
416 selected_images: List of images to output, or None for all
417 update_fdt: True to update the FDT wth entry offsets, etc.
0b6023ee
SG
418 use_expanded: True to use expanded versions of entries, if available.
419 So if 'u-boot' is called for, we use 'u-boot-expanded' instead. This
420 is needed if update_fdt is True (although tests may disable it)
e9d336d8
SG
421
422 Returns:
423 OrderedDict of images:
424 key: Image name (str)
425 value: Image object
a8573c4c
SG
426 """
427 # Import these here in case libfdt.py is not available, in which case
428 # the above help option still works.
16287933
SG
429 from dtoc import fdt
430 from dtoc import fdt_util
a8573c4c
SG
431 global images
432
433 # Get the device tree ready by compiling it and copying the compiled
434 # output into a file in our output directly. Then scan it for use
435 # in binman.
436 dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
437 fname = tools.GetOutputFilename('u-boot.dtb.out')
438 tools.WriteFile(fname, tools.ReadFile(dtb_fname))
439 dtb = fdt.FdtScan(fname)
440
441 node = _FindBinmanNode(dtb)
442 if not node:
443 raise ValueError("Device tree '%s' does not have a 'binman' "
444 "node" % dtb_fname)
445
0b6023ee 446 images = _ReadImageDesc(node, use_expanded)
a8573c4c
SG
447
448 if select_images:
449 skip = []
450 new_images = OrderedDict()
451 for name, image in images.items():
452 if name in select_images:
453 new_images[name] = image
454 else:
455 skip.append(name)
456 images = new_images
457 tout.Notice('Skipping images: %s' % ', '.join(skip))
458
459 state.Prepare(images, dtb)
460
461 # Prepare the device tree by making sure that any missing
462 # properties are added (e.g. 'pos' and 'size'). The values of these
463 # may not be correct yet, but we add placeholders so that the
464 # size of the device tree is correct. Later, in
465 # SetCalculatedProperties() we will insert the correct values
466 # without changing the device-tree size, thus ensuring that our
467 # entry offsets remain the same.
468 for image in images.values():
469 image.ExpandEntries()
470 if update_fdt:
a9fad07d 471 image.AddMissingProperties(True)
a8573c4c
SG
472 image.ProcessFdt(dtb)
473
4bdd1159 474 for dtb_item in state.GetAllFdts():
a8573c4c
SG
475 dtb_item.Sync(auto_resize=True)
476 dtb_item.Pack()
477 dtb_item.Flush()
478 return images
479
480
51014aab 481def ProcessImage(image, update_fdt, write_map, get_contents=True,
4f9f1056 482 allow_resize=True, allow_missing=False):
b88e81c6
SG
483 """Perform all steps for this image, including checking and # writing it.
484
485 This means that errors found with a later image will be reported after
486 earlier images are already completed and written, but that does not seem
487 important.
488
489 Args:
490 image: Image to process
491 update_fdt: True to update the FDT wth entry offsets, etc.
492 write_map: True to write a map file
10f9d006
SG
493 get_contents: True to get the image contents from files, etc., False if
494 the contents is already present
51014aab
SG
495 allow_resize: True to allow entries to change size (this does a re-pack
496 of the entries), False to raise an exception
4f9f1056 497 allow_missing: Allow blob_ext objects to be missing
b1cca955
SG
498
499 Returns:
500 True if one or more external blobs are missing, False if all are present
b88e81c6 501 """
10f9d006 502 if get_contents:
4f9f1056 503 image.SetAllowMissing(allow_missing)
10f9d006 504 image.GetEntryContents()
b88e81c6
SG
505 image.GetEntryOffsets()
506
507 # We need to pack the entries to figure out where everything
508 # should be placed. This sets the offset/size of each entry.
509 # However, after packing we call ProcessEntryContents() which
510 # may result in an entry changing size. In that case we need to
511 # do another pass. Since the device tree often contains the
512 # final offset/size information we try to make space for this in
513 # AddMissingProperties() above. However, if the device is
514 # compressed we cannot know this compressed size in advance,
515 # since changing an offset from 0x100 to 0x104 (for example) can
516 # alter the compressed size of the device tree. So we need a
517 # third pass for this.
eb0f4a4c 518 passes = 5
b88e81c6
SG
519 for pack_pass in range(passes):
520 try:
521 image.PackEntries()
b88e81c6
SG
522 except Exception as e:
523 if write_map:
524 fname = image.WriteMap()
525 print("Wrote map file '%s' to show errors" % fname)
526 raise
527 image.SetImagePos()
528 if update_fdt:
529 image.SetCalculatedProperties()
4bdd1159 530 for dtb_item in state.GetAllFdts():
b88e81c6 531 dtb_item.Sync()
51014aab 532 dtb_item.Flush()
261cbe0b 533 image.WriteSymbols()
b88e81c6
SG
534 sizes_ok = image.ProcessEntryContents()
535 if sizes_ok:
536 break
537 image.ResetForPack()
aed6c0b0 538 tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
b88e81c6 539 if not sizes_ok:
61ec04f9 540 image.Raise('Entries changed size after packing (tried %s passes)' %
b88e81c6
SG
541 passes)
542
b88e81c6
SG
543 image.BuildImage()
544 if write_map:
545 image.WriteMap()
b1cca955
SG
546 missing_list = []
547 image.CheckMissing(missing_list)
548 if missing_list:
549 tout.Warning("Image '%s' is missing external blobs and is non-functional: %s" %
550 (image.name, ' '.join([e.name for e in missing_list])))
b238143d 551 _ShowHelpForMissingBlobs(missing_list)
b1cca955 552 return bool(missing_list)
b88e81c6
SG
553
554
53cd5d92 555def Binman(args):
bf7fd50b
SG
556 """The main control code for binman
557
558 This assumes that help and test options have already been dealt with. It
559 deals with the core task of building images.
560
561 Args:
53cd5d92 562 args: Command line arguments Namespace object
bf7fd50b 563 """
8dbb7444
SG
564 global Image
565 global state
566
53cd5d92 567 if args.full_help:
bf7fd50b
SG
568 pager = os.getenv('PAGER')
569 if not pager:
570 pager = 'more'
571 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
61adb2d2 572 'README.rst')
bf7fd50b
SG
573 command.Run(pager, fname)
574 return 0
575
8dbb7444 576 # Put these here so that we can import this module without libfdt
07237988 577 from binman.image import Image
16287933 578 from binman import state
8dbb7444 579
7bc4f0f8 580 if args.cmd in ['ls', 'extract', 'replace']:
96b6c506 581 try:
7bc4f0f8 582 tout.Init(args.verbosity)
96b6c506 583 tools.PrepareOutputDir(None)
7bc4f0f8
SG
584 if args.cmd == 'ls':
585 ListEntries(args.image, args.paths)
586
587 if args.cmd == 'extract':
588 ExtractEntries(args.image, args.filename, args.outdir, args.paths,
589 not args.uncompressed)
590
591 if args.cmd == 'replace':
592 ReplaceEntries(args.image, args.filename, args.indir, args.paths,
593 do_compress=not args.compressed,
594 allow_resize=not args.fix_size, write_map=args.map)
595 except:
596 raise
a6cb9950
SG
597 finally:
598 tools.FinaliseOutputDir()
599 return 0
600
bf7fd50b 601 # Try to figure out which device tree contains our image description
53cd5d92
SG
602 if args.dt:
603 dtb_fname = args.dt
bf7fd50b 604 else:
53cd5d92 605 board = args.board
bf7fd50b
SG
606 if not board:
607 raise ValueError('Must provide a board to process (use -b <board>)')
53cd5d92 608 board_pathname = os.path.join(args.build_dir, board)
bf7fd50b 609 dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
53cd5d92
SG
610 if not args.indir:
611 args.indir = ['.']
612 args.indir.append(board_pathname)
bf7fd50b
SG
613
614 try:
53cd5d92
SG
615 tout.Init(args.verbosity)
616 elf.debug = args.debug
617 cbfs_util.VERBOSE = args.verbosity > 2
618 state.use_fake_dtb = args.fake_dtb
0b6023ee
SG
619
620 # Normally we replace the 'u-boot' etype with 'u-boot-expanded', etc.
621 # When running tests this can be disabled using this flag. When not
622 # updating the FDT in image, it is not needed by binman, but we use it
623 # for consistency, so that the images look the same to U-Boot at
624 # runtime.
625 use_expanded = not args.no_expanded
bf7fd50b 626 try:
53cd5d92
SG
627 tools.SetInputDirs(args.indir)
628 tools.PrepareOutputDir(args.outdir, args.preserve)
629 tools.SetToolPaths(args.toolpath)
630 state.SetEntryArgs(args.entry_arg)
ecab8973 631
a8573c4c 632 images = PrepareImagesAndDtbs(dtb_fname, args.image,
0b6023ee 633 args.update_fdt, use_expanded)
b1cca955 634 missing = False
bf7fd50b 635 for image in images.values():
b1cca955
SG
636 missing |= ProcessImage(image, args.update_fdt, args.map,
637 allow_missing=args.allow_missing)
2a72cc72
SG
638
639 # Write the updated FDTs to our output files
4bdd1159 640 for dtb_item in state.GetAllFdts():
2a72cc72
SG
641 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
642
b1cca955 643 if missing:
b238143d 644 tout.Warning("\nSome images are invalid")
bf7fd50b
SG
645 finally:
646 tools.FinaliseOutputDir()
647 finally:
648 tout.Uninit()
649
650 return 0
This page took 0.3312 seconds and 4 git commands to generate.