1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Entry-type module for producing a FIT
8 from collections import defaultdict, OrderedDict
11 from binman.entry import Entry, EntryArg
12 from dtoc import fdt_util
13 from dtoc.fdt import Fdt
14 from patman import tools
16 class Entry_fit(Entry):
17 """Flat Image Tree (FIT)
19 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
22 Nodes for the FIT should be written out in the binman configuration just as
23 they would be in a file passed to mkimage.
25 For example, this creates an image containing a FIT with U-Boot SPL::
29 description = "Test FIT";
30 fit,fdt-list = "of-list";
49 U-Boot supports creating fdt and config nodes automatically. To do this,
50 pass an of-list property (e.g. -a of-list=file1 file2). This tells binman
51 that you want to generates nodes for two files: file1.dtb and file2.dtb
52 The fit,fdt-list property (see above) indicates that of-list should be used.
53 If the property is missing you will get an error.
55 Then add a 'generator node', a node with a name starting with '@'::
59 description = "fdt-NAME";
65 This tells binman to create nodes fdt-1 and fdt-2 for each of your two
66 files. All the properties you specify will be included in the node. This
67 node acts like a template to generate the nodes. The generator node itself
68 does not appear in the output - it is replaced with what binman generates.
70 You can create config nodes in a similar way::
73 default = "@config-DEFAULT-SEQ";
82 This tells binman to create nodes config-1 and config-2, i.e. a config for
83 each of your two files.
85 Available substitutions for '@' nodes are:
88 Sequence number of the generated fdt (1, 2, ...)
90 Name of the dtb as provided (i.e. without adding '.dtb')
92 Note that if no devicetree files are provided (with '-a of-list' as above)
93 then no nodes will be generated.
95 The 'default' property, if present, will be automatically set to the name
96 if of configuration whose devicetree matches the 'default-dt' entry
97 argument, e.g. with '-a default-dt=sun50i-a64-pine64-lts'.
99 Available substitutions for '@' property values are
102 Sequence number of the default fdt,as provided by the 'default-dt' entry
105 Properties (in the 'fit' node itself):
106 fit,external-offset: Indicates that the contents of the FIT are external
107 and provides the external offset. This is passsed to mkimage via
111 def __init__(self, section, etype, node):
114 _fit: FIT file being built
116 key: relative path to entry Node (from the base of the FIT)
117 value: Entry_section object comprising the contents of this
120 super().__init__(section, etype, node)
122 self._fit_sections = {}
124 for pname, prop in self._node.props.items():
125 if pname.startswith('fit,'):
126 self._fit_props[pname] = prop
129 self._fit_list_prop = self._fit_props.get('fit,fdt-list')
130 if self._fit_list_prop:
131 fdts, = self.GetEntryArgsOrProps(
132 [EntryArg(self._fit_list_prop.value, str)])
134 self._fdts = fdts.split()
135 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
142 def _ReadSubnodes(self):
143 def _AddNode(base_node, depth, node):
144 """Add a node to the FIT
147 base_node: Base Node of the FIT (with 'description' property)
148 depth: Current node depth (0 is the base node)
149 node: Current node to process
151 There are two cases to deal with:
152 - hash and signature nodes which become part of the FIT
153 - binman entries which are used to define the 'data' for each
156 for pname, prop in node.props.items():
157 if not pname.startswith('fit,'):
158 if pname == 'default':
160 # Handle the 'default' property
161 if val.startswith('@'):
164 if not self._fit_default_dt:
165 self.Raise("Generated 'default' node requires default-dt entry argument")
166 if self._fit_default_dt not in self._fdts:
167 self.Raise("default-dt entry argument '%s' not found in fdt list: %s" %
168 (self._fit_default_dt,
169 ', '.join(self._fdts)))
170 seq = self._fdts.index(self._fit_default_dt)
171 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
172 fsw.property_string(pname, val)
174 fsw.property(pname, prop.bytes)
176 rel_path = node.path[len(base_node.path):]
177 in_images = rel_path.startswith('/images')
178 has_images = depth == 2 and in_images
180 # This node is a FIT subimage node (e.g. "/images/kernel")
181 # containing content nodes. We collect the subimage nodes and
182 # section entries for them here to merge the content subnodes
183 # together and put the merged contents in the subimage node's
184 # 'data' property later.
185 entry = Entry.Create(self.section, node, etype='section')
187 self._fit_sections[rel_path] = entry
189 for subnode in node.subnodes:
190 if has_images and not (subnode.name.startswith('hash') or
191 subnode.name.startswith('signature')):
192 # This subnode is a content node not meant to appear in
193 # the FIT (e.g. "/images/kernel/u-boot"), so don't call
194 # fsw.add_node() or _AddNode() for it.
196 elif subnode.name.startswith('@'):
198 # Generate notes for each FDT
199 for seq, fdt_fname in enumerate(self._fdts):
200 node_name = subnode.name[1:].replace('SEQ',
202 fname = tools.GetInputFilename(fdt_fname + '.dtb')
203 with fsw.add_node(node_name):
204 for pname, prop in subnode.props.items():
205 val = prop.bytes.replace(
206 b'NAME', tools.ToBytes(fdt_fname))
208 b'SEQ', tools.ToBytes(str(seq + 1)))
209 fsw.property(pname, val)
211 # Add data for 'fdt' nodes (but not 'config')
212 if depth == 1 and in_images:
214 tools.ReadFile(fname))
216 if self._fdts is None:
217 if self._fit_list_prop:
218 self.Raise("Generator node requires '%s' entry argument" %
219 self._fit_list_prop.value)
221 self.Raise("Generator node requires 'fit,fdt-list' property")
223 with fsw.add_node(subnode.name):
224 _AddNode(base_node, depth + 1, subnode)
226 # Build a new tree with all nodes and properties starting from the
229 fsw.finish_reservemap()
230 with fsw.add_node(''):
231 _AddNode(self._node, 0, self._node)
234 # Pack this new FDT and scan it so we can add the data later
236 self._fdt = Fdt.FromData(fdt.as_bytearray())
239 def ObtainContents(self):
240 """Obtain the contents of the FIT
242 This adds the 'data' properties to the input ITB (Image-tree Binary)
243 then runs mkimage to process it.
245 # self._BuildInput() either returns bytes or raises an exception.
246 data = self._BuildInput(self._fdt)
247 uniq = self.GetUniqueName()
248 input_fname = tools.GetOutputFilename('%s.itb' % uniq)
249 output_fname = tools.GetOutputFilename('%s.fit' % uniq)
250 tools.WriteFile(input_fname, data)
251 tools.WriteFile(output_fname, data)
254 ext_offset = self._fit_props.get('fit,external-offset')
255 if ext_offset is not None:
256 args += ['-E', '-p', '%x' % fdt_util.fdt32_to_cpu(ext_offset.value)]
257 tools.Run('mkimage', '-t', '-F', output_fname, *args)
259 self.SetContents(tools.ReadFile(output_fname))
262 def _BuildInput(self, fdt):
263 """Finish the FIT by adding the 'data' properties to it
269 New fdt contents (bytes)
271 for path, section in self._fit_sections.items():
272 node = fdt.GetNode(path)
273 # Entry_section.ObtainContents() either returns True or
274 # raises an exception.
275 section.ObtainContents()
277 data = section.GetData()
278 node.AddData('data', data)
280 fdt.Sync(auto_resize=True)
281 data = fdt.GetContents()
284 def CheckMissing(self, missing_list):
285 """Check if any entries in this FIT have missing external blobs
287 If there are missing blobs, the entries are added to the list
290 missing_list: List of Entry objects to be added to
292 for path, section in self._fit_sections.items():
293 section.CheckMissing(missing_list)
295 def SetAllowMissing(self, allow_missing):
296 for section in self._fit_sections.values():
297 section.SetAllowMissing(allow_missing)