• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Creates binary images from input files controlled by a description
6#
7
8from __future__ import print_function
9
10from collections import OrderedDict
11import os
12import sys
13import tools
14
15import cbfs_util
16import command
17import elf
18import 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
43def _FindBinmanNode(dtb):
44    """Find the 'binman' node in the device tree
45
46    Args:
47        dtb: Fdt object to scan
48    Returns:
49        Node object of /binman node, or None if not found
50    """
51    for node in dtb.GetRoot().subnodes:
52        if node.name == 'binman':
53            return node
54    return None
55
56def WriteEntryDocs(modules, test_missing=None):
57    """Write out documentation for all entries
58
59    Args:
60        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.
64    """
65    from entry import Entry
66    Entry.WriteDocs(modules, test_missing)
67
68
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
99
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    """
114    global Image
115    from image import Image
116
117    image = Image.FromFile(image_fname)
118    entry = image.FindEntryPath(entry_path)
119    return entry.ReadData(decomp)
120
121
122def 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
132        decomp: True to decompress the entry data
133
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:
142            raise ValueError('Must specify an entry path to write with -f')
143        if len(entry_paths) != 1:
144            raise ValueError('Must specify exactly one entry path to write with -f')
145        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
173def 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
229def WriteEntry(image_fname, entry_path, data, do_compress=True,
230               allow_resize=True, write_map=False):
231    """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
240        do_compress: True to compress the data if needed, False if data is
241            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
244        write_map: True to write a map file
245
246    Returns:
247        Image object that was updated
248    """
249    tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
250    image = Image.FromFile(image_fname)
251    entry = image.FindEntryPath(entry_path)
252    WriteEntryToImage(image, entry, data, do_compress=do_compress,
253                      allow_resize=allow_resize, write_map=write_map)
254
255    return image
256
257
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
322def 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
384    for dtb_item in state.GetAllFdts():
385        dtb_item.Sync(auto_resize=True)
386        dtb_item.Pack()
387        dtb_item.Flush()
388    return images
389
390
391def ProcessImage(image, update_fdt, write_map, get_contents=True,
392                 allow_resize=True):
393    """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
403        get_contents: True to get the image contents from files, etc., False if
404            the contents is already present
405        allow_resize: True to allow entries to change size (this does a re-pack
406            of the entries), False to raise an exception
407    """
408    if get_contents:
409        image.GetEntryContents()
410    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.
423    passes = 5
424    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()
437            for dtb_item in state.GetAllFdts():
438                dtb_item.Sync()
439                dtb_item.Flush()
440        image.WriteSymbols()
441        sizes_ok = image.ProcessEntryContents()
442        if sizes_ok:
443            break
444        image.ResetForPack()
445    tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
446    if not sizes_ok:
447        image.Raise('Entries changed size after packing (tried %s passes)' %
448                    passes)
449
450    image.BuildImage()
451    if write_map:
452        image.WriteMap()
453
454
455def Binman(args):
456    """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:
462        args: Command line arguments Namespace object
463    """
464    global Image
465    global state
466
467    if args.full_help:
468        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
476    # Put these here so that we can import this module without libfdt
477    from image import Image
478    import state
479
480    if args.cmd in ['ls', 'extract', 'replace']:
481        try:
482            tout.Init(args.verbosity)
483            tools.PrepareOutputDir(None)
484            if args.cmd == 'ls':
485                ListEntries(args.image, args.paths)
486
487            if args.cmd == 'extract':
488                ExtractEntries(args.image, args.filename, args.outdir, args.paths,
489                               not args.uncompressed)
490
491            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
497        finally:
498            tools.FinaliseOutputDir()
499        return 0
500
501    # Try to figure out which device tree contains our image description
502    if args.dt:
503        dtb_fname = args.dt
504    else:
505        board = args.board
506        if not board:
507            raise ValueError('Must provide a board to process (use -b <board>)')
508        board_pathname = os.path.join(args.build_dir, board)
509        dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
510        if not args.indir:
511            args.indir = ['.']
512        args.indir.append(board_pathname)
513
514    try:
515        tout.Init(args.verbosity)
516        elf.debug = args.debug
517        cbfs_util.VERBOSE = args.verbosity > 2
518        state.use_fake_dtb = args.fake_dtb
519        try:
520            tools.SetInputDirs(args.indir)
521            tools.PrepareOutputDir(args.outdir, args.preserve)
522            tools.SetToolPaths(args.toolpath)
523            state.SetEntryArgs(args.entry_arg)
524
525            images = PrepareImagesAndDtbs(dtb_fname, args.image,
526                                          args.update_fdt)
527            for image in images.values():
528                ProcessImage(image, args.update_fdt, args.map)
529
530            # Write the updated FDTs to our output files
531            for dtb_item in state.GetAllFdts():
532                tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
533
534        finally:
535            tools.FinaliseOutputDir()
536    finally:
537        tout.Uninit()
538
539    return 0
540