--- /dev/null
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for producing a FIT
+#
+
+from collections import defaultdict, OrderedDict
+import libfdt
+
+from binman.entry import Entry
+from dtoc import fdt_util
+from dtoc.fdt import Fdt
+from patman import tools
+
+class Entry_fit(Entry):
+ """Entry containing a FIT
+
+ This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
+ input provided.
+
+ Nodes for the FIT should be written out in the binman configuration just as
+ they would be in a file passed to mkimage.
+
+ For example, this creates an image containing a FIT with U-Boot SPL:
+
+ binman {
+ fit {
+ description = "Test FIT";
+
+ images {
+ kernel@1 {
+ description = "SPL";
+ os = "u-boot";
+ type = "rkspi";
+ arch = "arm";
+ compression = "none";
+ load = <0>;
+ entry = <0>;
+
+ u-boot-spl {
+ };
+ };
+ };
+ };
+ };
+
+ Properties:
+ fit,external-offset: Indicates that the contents of the FIT are external
+ and provides the external offset. This is passsed to mkimage via
+ the -E and -p flags.
+
+ """
+ def __init__(self, section, etype, node):
+ """
+ Members:
+ _fit: FIT file being built
+ _fit_content: dict:
+ key: relative path to entry Node (from the base of the FIT)
+ value: List of Entry objects comprising the contents of this
+ node
+ """
+ super().__init__(section, etype, node)
+ self._fit = None
+ self._fit_content = defaultdict(list)
+ self._fit_props = {}
+
+ def ReadNode(self):
+ self._ReadSubnodes()
+ super().ReadNode()
+
+ def _ReadSubnodes(self):
+ def _AddNode(base_node, depth, node):
+ """Add a node to the FIT
+
+ Args:
+ base_node: Base Node of the FIT (with 'description' property)
+ depth: Current node depth (0 is the base node)
+ node: Current node to process
+
+ There are two cases to deal with:
+ - hash and signature nodes which become part of the FIT
+ - binman entries which are used to define the 'data' for each
+ image
+ """
+ for pname, prop in node.props.items():
+ if pname.startswith('fit,'):
+ self._fit_props[pname] = prop
+ else:
+ fsw.property(pname, prop.bytes)
+
+ rel_path = node.path[len(base_node.path):]
+ has_images = depth == 2 and rel_path.startswith('/images/')
+ for subnode in node.subnodes:
+ if has_images and not (subnode.name.startswith('hash') or
+ subnode.name.startswith('signature')):
+ # This is a content node. We collect all of these together
+ # and put them in the 'data' property. They do not appear
+ # in the FIT.
+ entry = Entry.Create(self.section, subnode)
+ entry.ReadNode()
+ self._fit_content[rel_path].append(entry)
+ else:
+ with fsw.add_node(subnode.name):
+ _AddNode(base_node, depth + 1, subnode)
+
+ # Build a new tree with all nodes and properties starting from the
+ # entry node
+ fsw = libfdt.FdtSw()
+ fsw.finish_reservemap()
+ with fsw.add_node(''):
+ _AddNode(self._node, 0, self._node)
+ fdt = fsw.as_fdt()
+
+ # Pack this new FDT and scan it so we can add the data later
+ fdt.pack()
+ self._fdt = Fdt.FromData(fdt.as_bytearray())
+ self._fdt.Scan()
+
+ def ObtainContents(self):
+ """Obtain the contents of the FIT
+
+ This adds the 'data' properties to the input ITB (Image-tree Binary)
+ then runs mkimage to process it.
+ """
+ data = self._BuildInput(self._fdt)
+ if data == False:
+ return False
+ uniq = self.GetUniqueName()
+ input_fname = tools.GetOutputFilename('%s.itb' % uniq)
+ output_fname = tools.GetOutputFilename('%s.fit' % uniq)
+ tools.WriteFile(input_fname, data)
+ tools.WriteFile(output_fname, data)
+
+ args = []
+ ext_offset = self._fit_props.get('fit,external-offset')
+ if ext_offset is not None:
+ args += ['-E', '-p', '%x' % fdt_util.fdt32_to_cpu(ext_offset.value)]
+ tools.Run('mkimage', '-t', '-F', output_fname, *args)
+
+ self.SetContents(tools.ReadFile(output_fname))
+ return True
+
+ def _BuildInput(self, fdt):
+ """Finish the FIT by adding the 'data' properties to it
+
+ Arguments:
+ fdt: FIT to update
+
+ Returns:
+ New fdt contents (bytes)
+ """
+ for path, entries in self._fit_content.items():
+ node = fdt.GetNode(path)
+ data = b''
+ for entry in entries:
+ if not entry.ObtainContents():
+ return False
+ data += entry.GetData()
+ node.AddData('data', data)
+
+ fdt.Sync(auto_resize=True)
+ data = fdt.GetContents()
+ return data
#
# python -m unittest func_test.TestFunctional.testHelp
+import collections
import gzip
import hashlib
from optparse import OptionParser
import os
+import re
import shutil
import struct
import sys
"""Test that zero-size overlapping regions are ignored"""
self._DoTestFile('160_pack_overlap_zero.dts')
+ def testSimpleFit(self):
+ """Test an image with a FIT inside"""
+ data = self._DoReadFile('161_fit.dts')
+ self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
+ self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
+ fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
+
+ # The data should be inside the FIT
+ dtb = fdt.Fdt.FromData(fit_data)
+ dtb.Scan()
+ fnode = dtb.GetNode('/images/kernel')
+ self.assertIn('data', fnode.props)
+
+ fname = os.path.join(self._indir, 'fit_data.fit')
+ tools.WriteFile(fname, fit_data)
+ out = tools.Run('dumpimage', '-l', fname)
+
+ # Check a few features to make sure the plumbing works. We don't need
+ # to test the operation of mkimage or dumpimage here. First convert the
+ # output into a dict where the keys are the fields printed by dumpimage
+ # and the values are a list of values for each field
+ lines = out.splitlines()
+
+ # Converts "Compression: gzip compressed" into two groups:
+ # 'Compression' and 'gzip compressed'
+ re_line = re.compile(r'^ *([^:]*)(?:: *(.*))?$')
+ vals = collections.defaultdict(list)
+ for line in lines:
+ mat = re_line.match(line)
+ vals[mat.group(1)].append(mat.group(2))
+
+ self.assertEquals('FIT description: test-desc', lines[0])
+ self.assertIn('Created:', lines[1])
+ self.assertIn('Image 0 (kernel)', vals)
+ self.assertIn('Hash value', vals)
+ data_sizes = vals.get('Data Size')
+ self.assertIsNotNone(data_sizes)
+ self.assertEqual(2, len(data_sizes))
+ # Format is "4 Bytes = 0.00 KiB = 0.00 MiB" so take the first word
+ self.assertEqual(len(U_BOOT_DATA), int(data_sizes[0].split()[0]))
+ self.assertEqual(len(U_BOOT_SPL_DTB_DATA), int(data_sizes[1].split()[0]))
+
+ def testFitExternal(self):
+ """Test an image with an FIT"""
+ data = self._DoReadFile('162_fit_external.dts')
+ fit_data = data[len(U_BOOT_DATA):-2] # _testing is 2 bytes
+
+ # The data should be outside the FIT
+ dtb = fdt.Fdt.FromData(fit_data)
+ dtb.Scan()
+ fnode = dtb.GetNode('/images/kernel')
+ self.assertNotIn('data', fnode.props)
if __name__ == "__main__":
unittest.main()