binman: Drop repetitive heading for each entry
[platform/kernel/u-boot.git] / tools / binman / etype / fit.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Entry-type module for producing a FIT
6 #
7
8 from collections import defaultdict, OrderedDict
9 import libfdt
10
11 from binman.entry import Entry, EntryArg
12 from dtoc import fdt_util
13 from dtoc.fdt import Fdt
14 from patman import tools
15
16 class Entry_fit(Entry):
17     """Flat Image Tree (FIT)
18
19     This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
20     input provided.
21
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.
24
25     For example, this creates an image containing a FIT with U-Boot SPL::
26
27         binman {
28             fit {
29                 description = "Test FIT";
30                 fit,fdt-list = "of-list";
31
32                 images {
33                     kernel@1 {
34                         description = "SPL";
35                         os = "u-boot";
36                         type = "rkspi";
37                         arch = "arm";
38                         compression = "none";
39                         load = <0>;
40                         entry = <0>;
41
42                         u-boot-spl {
43                         };
44                     };
45                 };
46             };
47         };
48
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.
54
55     Then add a 'generator node', a node with a name starting with '@'::
56
57         images {
58             @fdt-SEQ {
59                 description = "fdt-NAME";
60                 type = "flat_dt";
61                 compression = "none";
62             };
63         };
64
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.
69
70     You can create config nodes in a similar way::
71
72         configurations {
73             default = "@config-DEFAULT-SEQ";
74             @config-SEQ {
75                 description = "NAME";
76                 firmware = "atf";
77                 loadables = "uboot";
78                 fdt = "fdt-SEQ";
79             };
80         };
81
82     This tells binman to create nodes config-1 and config-2, i.e. a config for
83     each of your two files.
84
85     Available substitutions for '@' nodes are:
86
87     SEQ:
88         Sequence number of the generated fdt (1, 2, ...)
89     NAME
90         Name of the dtb as provided (i.e. without adding '.dtb')
91
92     Note that if no devicetree files are provided (with '-a of-list' as above)
93     then no nodes will be generated.
94
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'.
98
99     Available substitutions for '@' property values are
100
101     DEFAULT-SEQ:
102         Sequence number of the default fdt,as provided by the 'default-dt' entry
103         argument
104
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
108             the -E and -p flags.
109
110     """
111     def __init__(self, section, etype, node):
112         """
113         Members:
114             _fit: FIT file being built
115             _fit_sections: dict:
116                 key: relative path to entry Node (from the base of the FIT)
117                 value: Entry_section object comprising the contents of this
118                     node
119         """
120         super().__init__(section, etype, node)
121         self._fit = None
122         self._fit_sections = {}
123         self._fit_props = {}
124         for pname, prop in self._node.props.items():
125             if pname.startswith('fit,'):
126                 self._fit_props[pname] = prop
127
128         self._fdts = None
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)])
133             if fdts is not None:
134                 self._fdts = fdts.split()
135         self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
136                                                                   str)])[0]
137
138     def ReadNode(self):
139         self._ReadSubnodes()
140         super().ReadNode()
141
142     def _ReadSubnodes(self):
143         def _AddNode(base_node, depth, node):
144             """Add a node to the FIT
145
146             Args:
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
150
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
154                   image
155             """
156             for pname, prop in node.props.items():
157                 if not pname.startswith('fit,'):
158                     if pname == 'default':
159                         val = prop.value
160                         # Handle the 'default' property
161                         if val.startswith('@'):
162                             if not self._fdts:
163                                 continue
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)
173                             continue
174                     fsw.property(pname, prop.bytes)
175
176             rel_path = node.path[len(base_node.path):]
177             in_images = rel_path.startswith('/images')
178             has_images = depth == 2 and in_images
179             if has_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')
186                 entry.ReadNode()
187                 self._fit_sections[rel_path] = entry
188
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.
195                     pass
196                 elif subnode.name.startswith('@'):
197                     if self._fdts:
198                         # Generate notes for each FDT
199                         for seq, fdt_fname in enumerate(self._fdts):
200                             node_name = subnode.name[1:].replace('SEQ',
201                                                                  str(seq + 1))
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))
207                                     val = val.replace(
208                                         b'SEQ', tools.ToBytes(str(seq + 1)))
209                                     fsw.property(pname, val)
210
211                                 # Add data for 'fdt' nodes (but not 'config')
212                                 if depth == 1 and in_images:
213                                     fsw.property('data',
214                                                  tools.ReadFile(fname))
215                     else:
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)
220                             else:
221                                 self.Raise("Generator node requires 'fit,fdt-list' property")
222                 else:
223                     with fsw.add_node(subnode.name):
224                         _AddNode(base_node, depth + 1, subnode)
225
226         # Build a new tree with all nodes and properties starting from the
227         # entry node
228         fsw = libfdt.FdtSw()
229         fsw.finish_reservemap()
230         with fsw.add_node(''):
231             _AddNode(self._node, 0, self._node)
232         fdt = fsw.as_fdt()
233
234         # Pack this new FDT and scan it so we can add the data later
235         fdt.pack()
236         self._fdt = Fdt.FromData(fdt.as_bytearray())
237         self._fdt.Scan()
238
239     def ObtainContents(self):
240         """Obtain the contents of the FIT
241
242         This adds the 'data' properties to the input ITB (Image-tree Binary)
243         then runs mkimage to process it.
244         """
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)
252
253         args = []
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)
258
259         self.SetContents(tools.ReadFile(output_fname))
260         return True
261
262     def _BuildInput(self, fdt):
263         """Finish the FIT by adding the 'data' properties to it
264
265         Arguments:
266             fdt: FIT to update
267
268         Returns:
269             New fdt contents (bytes)
270         """
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()
276             section.Pack(0)
277             data = section.GetData()
278             node.AddData('data', data)
279
280         fdt.Sync(auto_resize=True)
281         data = fdt.GetContents()
282         return data
283
284     def CheckMissing(self, missing_list):
285         """Check if any entries in this FIT have missing external blobs
286
287         If there are missing blobs, the entries are added to the list
288
289         Args:
290             missing_list: List of Entry objects to be added to
291         """
292         for path, section in self._fit_sections.items():
293             section.CheckMissing(missing_list)
294
295     def SetAllowMissing(self, allow_missing):
296         for section in self._fit_sections.values():
297             section.SetAllowMissing(allow_missing)