binman: Move Image.BuildImage() into a single function
[platform/kernel/u-boot.git] / tools / binman / state.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Holds and modifies the state information held by binman
6 #
7
8 import hashlib
9 import re
10
11 import fdt
12 import os
13 import tools
14 import tout
15
16 # Records the device-tree files known to binman, keyed by entry type (e.g.
17 # 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
18 # binman. They have been copied to <xxx>.out files.
19 #
20 #   key: entry type
21 #   value: tuple:
22 #       Fdt object
23 #       Filename
24 #       Entry object, or None if not known
25 output_fdt_info = {}
26
27 # Prefix to add to an fdtmap path to turn it into a path to the /binman node
28 fdt_path_prefix = ''
29
30 # Arguments passed to binman to provide arguments to entries
31 entry_args = {}
32
33 # True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
34 # ftest.py)
35 use_fake_dtb = False
36
37 # The DTB which contains the full image information
38 main_dtb = None
39
40 # Allow entries to expand after they have been packed. This is detected and
41 # forces a re-pack. If not allowed, any attempted expansion causes an error in
42 # Entry.ProcessContentsUpdate()
43 allow_entry_expansion = True
44
45 def GetFdtForEtype(etype):
46     """Get the Fdt object for a particular device-tree entry
47
48     Binman keeps track of at least one device-tree file called u-boot.dtb but
49     can also have others (e.g. for SPL). This function looks up the given
50     entry and returns the associated Fdt object.
51
52     Args:
53         etype: Entry type of device tree (e.g. 'u-boot-dtb')
54
55     Returns:
56         Fdt object associated with the entry type
57     """
58     value = output_fdt_info.get(etype);
59     if not value:
60         return None
61     return value[0]
62
63 def GetFdtPath(etype):
64     """Get the full pathname of a particular Fdt object
65
66     Similar to GetFdtForEtype() but returns the pathname associated with the
67     Fdt.
68
69     Args:
70         etype: Entry type of device tree (e.g. 'u-boot-dtb')
71
72     Returns:
73         Full path name to the associated Fdt
74     """
75     return output_fdt_info[etype][0]._fname
76
77 def GetFdtContents(etype='u-boot-dtb'):
78     """Looks up the FDT pathname and contents
79
80     This is used to obtain the Fdt pathname and contents when needed by an
81     entry. It supports a 'fake' dtb, allowing tests to substitute test data for
82     the real dtb.
83
84     Args:
85         etype: Entry type to look up (e.g. 'u-boot.dtb').
86
87     Returns:
88         tuple:
89             pathname to Fdt
90             Fdt data (as bytes)
91     """
92     if etype not in output_fdt_info:
93         return None, None
94     if not use_fake_dtb:
95         pathname = GetFdtPath(etype)
96         data = GetFdtForEtype(etype).GetContents()
97     else:
98         fname = output_fdt_info[etype][1]
99         pathname = tools.GetInputFilename(fname)
100         data = tools.ReadFile(pathname)
101     return pathname, data
102
103 def SetEntryArgs(args):
104     """Set the value of the entry args
105
106     This sets up the entry_args dict which is used to supply entry arguments to
107     entries.
108
109     Args:
110         args: List of entry arguments, each in the format "name=value"
111     """
112     global entry_args
113
114     entry_args = {}
115     if args:
116         for arg in args:
117             m = re.match('([^=]*)=(.*)', arg)
118             if not m:
119                 raise ValueError("Invalid entry arguemnt '%s'" % arg)
120             entry_args[m.group(1)] = m.group(2)
121
122 def GetEntryArg(name):
123     """Get the value of an entry argument
124
125     Args:
126         name: Name of argument to retrieve
127
128     Returns:
129         String value of argument
130     """
131     return entry_args.get(name)
132
133 def Prepare(images, dtb):
134     """Get device tree files ready for use
135
136     This sets up a set of device tree files that can be retrieved by
137     GetAllFdts(). This includes U-Boot proper and any SPL device trees.
138
139     Args:
140         images: List of images being used
141         dtb: Main dtb
142     """
143     global output_fdt_info, main_dtb, fdt_path_prefix
144     # Import these here in case libfdt.py is not available, in which case
145     # the above help option still works.
146     import fdt
147     import fdt_util
148
149     # If we are updating the DTBs we need to put these updated versions
150     # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
151     # since it is assumed to be the one passed in with options.dt, and
152     # was handled just above.
153     main_dtb = dtb
154     output_fdt_info.clear()
155     fdt_path_prefix = ''
156     output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb', None]
157     output_fdt_info['u-boot-spl-dtb'] = [dtb, 'spl/u-boot-spl.dtb', None]
158     output_fdt_info['u-boot-tpl-dtb'] = [dtb, 'tpl/u-boot-tpl.dtb', None]
159     if not use_fake_dtb:
160         fdt_set = {}
161         for image in images.values():
162             fdt_set.update(image.GetFdts())
163         for etype, other in fdt_set.items():
164             entry, other_fname = other
165             infile = tools.GetInputFilename(other_fname)
166             other_fname_dtb = fdt_util.EnsureCompiled(infile)
167             out_fname = tools.GetOutputFilename('%s.out' %
168                     os.path.split(other_fname)[1])
169             tools.WriteFile(out_fname, tools.ReadFile(other_fname_dtb))
170             other_dtb = fdt.FdtScan(out_fname)
171             output_fdt_info[etype] = [other_dtb, out_fname, entry]
172
173 def PrepareFromLoadedData(image):
174     """Get device tree files ready for use with a loaded image
175
176     Loaded images are different from images that are being created by binman,
177     since there is generally already an fdtmap and we read the description from
178     that. This provides the position and size of every entry in the image with
179     no calculation required.
180
181     This function uses the same output_fdt_info[] as Prepare(). It finds the
182     device tree files, adds a reference to the fdtmap and sets the FDT path
183     prefix to translate from the fdtmap (where the root node is the image node)
184     to the normal device tree (where the image node is under a /binman node).
185
186     Args:
187         images: List of images being used
188     """
189     global output_fdt_info, main_dtb, fdt_path_prefix
190
191     tout.Info('Preparing device trees')
192     output_fdt_info.clear()
193     fdt_path_prefix = ''
194     output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb', None]
195     main_dtb = None
196     tout.Info("   Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
197     for etype, value in image.GetFdts().items():
198         entry, fname = value
199         out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype)
200         tout.Info("   Found device tree type '%s' at '%s' path '%s'" %
201                   (etype, out_fname, entry.GetPath()))
202         entry._filename = entry.GetDefaultFilename()
203         data = entry.ReadData()
204
205         tools.WriteFile(out_fname, data)
206         dtb = fdt.Fdt(out_fname)
207         dtb.Scan()
208         image_node = dtb.GetNode('/binman')
209         if 'multiple-images' in image_node.props:
210             image_node = dtb.GetNode('/binman/%s' % image.image_node)
211         fdt_path_prefix = image_node.path
212         output_fdt_info[etype] = [dtb, None, entry]
213     tout.Info("   FDT path prefix '%s'" % fdt_path_prefix)
214
215
216 def GetAllFdts():
217     """Yield all device tree files being used by binman
218
219     Yields:
220         Device trees being used (U-Boot proper, SPL, TPL)
221     """
222     if main_dtb:
223         yield main_dtb
224     for etype in output_fdt_info:
225         dtb = output_fdt_info[etype][0]
226         if dtb != main_dtb:
227             yield dtb
228
229 def GetUpdateNodes(node, for_repack=False):
230     """Yield all the nodes that need to be updated in all device trees
231
232     The property referenced by this node is added to any device trees which
233     have the given node. Due to removable of unwanted notes, SPL and TPL may
234     not have this node.
235
236     Args:
237         node: Node object in the main device tree to look up
238         for_repack: True if we want only nodes which need 'repack' properties
239             added to them (e.g. 'orig-offset'), False to return all nodes. We
240             don't add repack properties to SPL/TPL device trees.
241
242     Yields:
243         Node objects in each device tree that is in use (U-Boot proper, which
244             is node, SPL and TPL)
245     """
246     yield node
247     for dtb, fname, entry in output_fdt_info.values():
248         if dtb != node.GetFdt():
249             if for_repack and entry.etype != 'u-boot-dtb':
250                 continue
251             other_node = dtb.GetNode(fdt_path_prefix + node.path)
252             if other_node:
253                 yield other_node
254
255 def AddZeroProp(node, prop, for_repack=False):
256     """Add a new property to affected device trees with an integer value of 0.
257
258     Args:
259         prop_name: Name of property
260         for_repack: True is this property is only needed for repacking
261     """
262     for n in GetUpdateNodes(node, for_repack):
263         n.AddZeroProp(prop)
264
265 def AddSubnode(node, name):
266     """Add a new subnode to a node in affected device trees
267
268     Args:
269         node: Node to add to
270         name: name of node to add
271
272     Returns:
273         New subnode that was created in main tree
274     """
275     first = None
276     for n in GetUpdateNodes(node):
277         subnode = n.AddSubnode(name)
278         if not first:
279             first = subnode
280     return first
281
282 def AddString(node, prop, value):
283     """Add a new string property to affected device trees
284
285     Args:
286         prop_name: Name of property
287         value: String value (which will be \0-terminated in the DT)
288     """
289     for n in GetUpdateNodes(node):
290         n.AddString(prop, value)
291
292 def SetInt(node, prop, value, for_repack=False):
293     """Update an integer property in affected device trees with an integer value
294
295     This is not allowed to change the size of the FDT.
296
297     Args:
298         prop_name: Name of property
299         for_repack: True is this property is only needed for repacking
300     """
301     for n in GetUpdateNodes(node, for_repack):
302         tout.Detail("File %s: Update node '%s' prop '%s' to %#x" %
303                     (node.GetFdt().name, node.path, prop, value))
304         n.SetInt(prop, value)
305
306 def CheckAddHashProp(node):
307     hash_node = node.FindNode('hash')
308     if hash_node:
309         algo = hash_node.props.get('algo')
310         if not algo:
311             return "Missing 'algo' property for hash node"
312         if algo.value == 'sha256':
313             size = 32
314         else:
315             return "Unknown hash algorithm '%s'" % algo
316         for n in GetUpdateNodes(hash_node):
317             n.AddEmptyProp('value', size)
318
319 def CheckSetHashValue(node, get_data_func):
320     hash_node = node.FindNode('hash')
321     if hash_node:
322         algo = hash_node.props.get('algo').value
323         if algo == 'sha256':
324             m = hashlib.sha256()
325             m.update(get_data_func())
326             data = m.digest()
327         for n in GetUpdateNodes(hash_node):
328             n.SetData('value', data)
329
330 def SetAllowEntryExpansion(allow):
331     """Set whether post-pack expansion of entries is allowed
332
333     Args:
334        allow: True to allow expansion, False to raise an exception
335     """
336     global allow_entry_expansion
337
338     allow_entry_expansion = allow
339
340 def AllowEntryExpansion():
341     """Check whether post-pack expansion of entries is allowed
342
343     Returns:
344         True if expansion should be allowed, False if an exception should be
345             raised
346     """
347     return allow_entry_expansion