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 """Entry containing a 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:
87 SEQ Sequence number of the generated fdt (1, 2, ...)
88 NAME Name of the dtb as provided (i.e. without adding '.dtb')
90 Note that if no devicetree files are provided (with '-a of-list' as above)
91 then no nodes will be generated.
93 The 'default' property, if present, will be automatically set to the name
94 if of configuration whose devicetree matches the 'default-dt' entry
95 argument, e.g. with '-a default-dt=sun50i-a64-pine64-lts'.
97 Available substitutions for '@' property values are:
99 DEFAULT-SEQ Sequence number of the default fdt,as provided by the
100 'default-dt' entry argument
102 Properties (in the 'fit' node itself):
103 fit,external-offset: Indicates that the contents of the FIT are external
104 and provides the external offset. This is passsed to mkimage via
108 def __init__(self, section, etype, node):
111 _fit: FIT file being built
113 key: relative path to entry Node (from the base of the FIT)
114 value: Entry_section object comprising the contents of this
117 super().__init__(section, etype, node)
119 self._fit_sections = {}
121 for pname, prop in self._node.props.items():
122 if pname.startswith('fit,'):
123 self._fit_props[pname] = prop
126 self._fit_list_prop = self._fit_props.get('fit,fdt-list')
127 if self._fit_list_prop:
128 fdts, = self.GetEntryArgsOrProps(
129 [EntryArg(self._fit_list_prop.value, str)])
131 self._fdts = fdts.split()
132 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
139 def _ReadSubnodes(self):
140 def _AddNode(base_node, depth, node):
141 """Add a node to the FIT
144 base_node: Base Node of the FIT (with 'description' property)
145 depth: Current node depth (0 is the base node)
146 node: Current node to process
148 There are two cases to deal with:
149 - hash and signature nodes which become part of the FIT
150 - binman entries which are used to define the 'data' for each
153 for pname, prop in node.props.items():
154 if not pname.startswith('fit,'):
155 if pname == 'default':
157 # Handle the 'default' property
158 if val.startswith('@'):
161 if not self._fit_default_dt:
162 self.Raise("Generated 'default' node requires default-dt entry argument")
163 if self._fit_default_dt not in self._fdts:
164 self.Raise("default-dt entry argument '%s' not found in fdt list: %s" %
165 (self._fit_default_dt,
166 ', '.join(self._fdts)))
167 seq = self._fdts.index(self._fit_default_dt)
168 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
169 fsw.property_string(pname, val)
171 fsw.property(pname, prop.bytes)
173 rel_path = node.path[len(base_node.path):]
174 in_images = rel_path.startswith('/images')
175 has_images = depth == 2 and in_images
177 # This node is a FIT subimage node (e.g. "/images/kernel")
178 # containing content nodes. We collect the subimage nodes and
179 # section entries for them here to merge the content subnodes
180 # together and put the merged contents in the subimage node's
181 # 'data' property later.
182 entry = Entry.Create(self.section, node, etype='section')
184 self._fit_sections[rel_path] = entry
186 for subnode in node.subnodes:
187 if has_images and not (subnode.name.startswith('hash') or
188 subnode.name.startswith('signature')):
189 # This subnode is a content node not meant to appear in
190 # the FIT (e.g. "/images/kernel/u-boot"), so don't call
191 # fsw.add_node() or _AddNode() for it.
193 elif subnode.name.startswith('@'):
195 # Generate notes for each FDT
196 for seq, fdt_fname in enumerate(self._fdts):
197 node_name = subnode.name[1:].replace('SEQ',
199 fname = tools.GetInputFilename(fdt_fname + '.dtb')
200 with fsw.add_node(node_name):
201 for pname, prop in subnode.props.items():
202 val = prop.bytes.replace(
203 b'NAME', tools.ToBytes(fdt_fname))
205 b'SEQ', tools.ToBytes(str(seq + 1)))
206 fsw.property(pname, val)
208 # Add data for 'fdt' nodes (but not 'config')
209 if depth == 1 and in_images:
211 tools.ReadFile(fname))
213 if self._fdts is None:
214 if self._fit_list_prop:
215 self.Raise("Generator node requires '%s' entry argument" %
216 self._fit_list_prop.value)
218 self.Raise("Generator node requires 'fit,fdt-list' property")
220 with fsw.add_node(subnode.name):
221 _AddNode(base_node, depth + 1, subnode)
223 # Build a new tree with all nodes and properties starting from the
226 fsw.finish_reservemap()
227 with fsw.add_node(''):
228 _AddNode(self._node, 0, self._node)
231 # Pack this new FDT and scan it so we can add the data later
233 self._fdt = Fdt.FromData(fdt.as_bytearray())
236 def ObtainContents(self):
237 """Obtain the contents of the FIT
239 This adds the 'data' properties to the input ITB (Image-tree Binary)
240 then runs mkimage to process it.
242 # self._BuildInput() either returns bytes or raises an exception.
243 data = self._BuildInput(self._fdt)
244 uniq = self.GetUniqueName()
245 input_fname = tools.GetOutputFilename('%s.itb' % uniq)
246 output_fname = tools.GetOutputFilename('%s.fit' % uniq)
247 tools.WriteFile(input_fname, data)
248 tools.WriteFile(output_fname, data)
251 ext_offset = self._fit_props.get('fit,external-offset')
252 if ext_offset is not None:
253 args += ['-E', '-p', '%x' % fdt_util.fdt32_to_cpu(ext_offset.value)]
254 tools.Run('mkimage', '-t', '-F', output_fname, *args)
256 self.SetContents(tools.ReadFile(output_fname))
259 def _BuildInput(self, fdt):
260 """Finish the FIT by adding the 'data' properties to it
266 New fdt contents (bytes)
268 for path, section in self._fit_sections.items():
269 node = fdt.GetNode(path)
270 # Entry_section.ObtainContents() either returns True or
271 # raises an exception.
272 section.ObtainContents()
274 data = section.GetData()
275 node.AddData('data', data)
277 fdt.Sync(auto_resize=True)
278 data = fdt.GetContents()
281 def CheckMissing(self, missing_list):
282 """Check if any entries in this FIT have missing external blobs
284 If there are missing blobs, the entries are added to the list
287 missing_list: List of Entry objects to be added to
289 for path, section in self._fit_sections.items():
290 section.CheckMissing(missing_list)
292 def SetAllowMissing(self, allow_missing):
293 for section in self._fit_sections.values():
294 section.SetAllowMissing(allow_missing)