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