]> Git Repo - J-u-boot.git/blob - tools/binman/state.py
Merge tag 'u-boot-imx-master-20250127' of https://gitlab.denx.de/u-boot/custodians...
[J-u-boot.git] / tools / binman / state.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2018 Google, Inc
3 # Written by Simon Glass <[email protected]>
4 #
5 # Holds and modifies the state information held by binman
6 #
7
8 from collections import defaultdict
9 import hashlib
10 import re
11 import time
12 import threading
13
14 from dtoc import fdt
15 import os
16 from u_boot_pylib import tools
17 from u_boot_pylib import tout
18
19 OUR_PATH = os.path.dirname(os.path.realpath(__file__))
20
21 # Map an dtb etype to its expected filename
22 DTB_TYPE_FNAME = {
23     'u-boot-spl-dtb': 'spl/u-boot-spl.dtb',
24     'u-boot-tpl-dtb': 'tpl/u-boot-tpl.dtb',
25     'u-boot-vpl-dtb': 'vpl/u-boot-vpl.dtb',
26     }
27
28 # Records the device-tree files known to binman, keyed by entry type (e.g.
29 # 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
30 # binman. They have been copied to <xxx>.out files.
31 #
32 #   key: entry type (e.g. 'u-boot-dtb)
33 #   value: tuple:
34 #       Fdt object
35 #       Filename
36 output_fdt_info = {}
37
38 # Prefix to add to an fdtmap path to turn it into a path to the /binman node
39 fdt_path_prefix = ''
40
41 # Arguments passed to binman to provide arguments to entries
42 entry_args = {}
43
44 # True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
45 # ftest.py)
46 use_fake_dtb = False
47
48 # The DTB which contains the full image information
49 main_dtb = None
50
51 # Allow entries to expand after they have been packed. This is detected and
52 # forces a re-pack. If not allowed, any attempted expansion causes an error in
53 # Entry.ProcessContentsUpdate()
54 allow_entry_expansion = True
55
56 # Don't allow entries to contract after they have been packed. Instead just
57 # leave some wasted space. If allowed, this is detected and forces a re-pack,
58 # but may result in entries that oscillate in size, thus causing a pack error.
59 # An example is a compressed device tree where the original offset values
60 # result in a larger compressed size than the new ones, but then after updating
61 # to the new ones, the compressed size increases, etc.
62 allow_entry_contraction = False
63
64 # Number of threads to use for binman (None means machine-dependent)
65 num_threads = None
66
67
68 class Timing:
69     """Holds information about an operation that is being timed
70
71     Properties:
72         name: Operation name (only one of each name is stored)
73         start: Start time of operation in seconds (None if not start)
74         accum:: Amount of time spent on this operation so far, in seconds
75     """
76     def __init__(self, name):
77         self.name = name
78         self.start = None # cause an error if TimingStart() is not called
79         self.accum = 0.0
80
81
82 # Holds timing info for each name:
83 #    key: name of Timing info (Timing.name)
84 #    value: Timing object
85 timing_info = {}
86
87
88 def GetFdtForEtype(etype):
89     """Get the Fdt object for a particular device-tree entry
90
91     Binman keeps track of at least one device-tree file called u-boot.dtb but
92     can also have others (e.g. for SPL). This function looks up the given
93     entry and returns the associated Fdt object.
94
95     Args:
96         etype: Entry type of device tree (e.g. 'u-boot-dtb')
97
98     Returns:
99         Fdt object associated with the entry type
100     """
101     value = output_fdt_info.get(etype);
102     if not value:
103         return None
104     return value[0]
105
106 def GetFdtPath(etype):
107     """Get the full pathname of a particular Fdt object
108
109     Similar to GetFdtForEtype() but returns the pathname associated with the
110     Fdt.
111
112     Args:
113         etype: Entry type of device tree (e.g. 'u-boot-dtb')
114
115     Returns:
116         Full path name to the associated Fdt
117     """
118     return output_fdt_info[etype][0]._fname
119
120 def GetFdtContents(etype='u-boot-dtb'):
121     """Looks up the FDT pathname and contents
122
123     This is used to obtain the Fdt pathname and contents when needed by an
124     entry. It supports a 'fake' dtb, allowing tests to substitute test data for
125     the real dtb.
126
127     Args:
128         etype: Entry type to look up (e.g. 'u-boot.dtb').
129
130     Returns:
131         tuple:
132             pathname to Fdt
133             Fdt data (as bytes)
134     """
135     if etype not in output_fdt_info:
136         return None, None
137     if not use_fake_dtb:
138         pathname = GetFdtPath(etype)
139         data = GetFdtForEtype(etype).GetContents()
140     else:
141         fname = output_fdt_info[etype][1]
142         pathname = tools.get_input_filename(fname)
143         data = tools.read_file(pathname)
144     return pathname, data
145
146 def UpdateFdtContents(etype, data):
147     """Update the contents of a particular device tree
148
149     The device tree is updated and written back to its file. This affects what
150     is returned from future called to GetFdtContents(), etc.
151
152     Args:
153         etype: Entry type (e.g. 'u-boot-dtb')
154         data: Data to replace the DTB with
155     """
156     dtb, fname = output_fdt_info[etype]
157     dtb_fname = dtb.GetFilename()
158     tools.write_file(dtb_fname, data)
159     dtb = fdt.FdtScan(dtb_fname)
160     output_fdt_info[etype] = [dtb, fname]
161
162 def SetEntryArgs(args):
163     """Set the value of the entry args
164
165     This sets up the entry_args dict which is used to supply entry arguments to
166     entries.
167
168     Args:
169         args: List of entry arguments, each in the format "name=value"
170     """
171     global entry_args
172
173     entry_args = {}
174     tout.debug('Processing entry args:')
175     if args:
176         for arg in args:
177             m = re.match('([^=]*)=(.*)', arg)
178             if not m:
179                 raise ValueError("Invalid entry arguemnt '%s'" % arg)
180             name, value = m.groups()
181             tout.debug('   %20s = %s' % (name, value))
182             entry_args[name] = value
183     tout.debug('Processing entry args done')
184
185 def GetEntryArg(name):
186     """Get the value of an entry argument
187
188     Args:
189         name: Name of argument to retrieve
190
191     Returns:
192         String value of argument
193     """
194     return entry_args.get(name)
195
196 def GetEntryArgBool(name):
197     """Get the value of an entry argument as a boolean
198
199     Args:
200         name: Name of argument to retrieve
201
202     Returns:
203         False if the entry argument is consider False (empty, '0' or 'n'), else
204             True
205     """
206     val = GetEntryArg(name)
207     return val and val not in ['n', '0']
208
209 def Prepare(images, dtb):
210     """Get device tree files ready for use
211
212     This sets up a set of device tree files that can be retrieved by
213     GetAllFdts(). This includes U-Boot proper and any SPL device trees.
214
215     Args:
216         images: List of images being used
217         dtb: Main dtb
218     """
219     global output_fdt_info, main_dtb, fdt_path_prefix
220     # Import these here in case libfdt.py is not available, in which case
221     # the above help option still works.
222     from dtoc import fdt
223     from dtoc import fdt_util
224
225     # If we are updating the DTBs we need to put these updated versions
226     # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
227     # since it is assumed to be the one passed in with options.dt, and
228     # was handled just above.
229     main_dtb = dtb
230     output_fdt_info.clear()
231     fdt_path_prefix = ''
232     output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb']
233     if use_fake_dtb:
234         for etype, fname in DTB_TYPE_FNAME.items():
235             output_fdt_info[etype] = [dtb, fname]
236     else:
237         fdt_set = {}
238         for etype, fname in DTB_TYPE_FNAME.items():
239             infile = tools.get_input_filename(fname, allow_missing=True)
240             if infile and os.path.exists(infile):
241                 fname_dtb = fdt_util.EnsureCompiled(infile)
242                 out_fname = tools.get_output_filename('%s.out' %
243                         os.path.split(fname)[1])
244                 tools.write_file(out_fname, tools.read_file(fname_dtb))
245                 other_dtb = fdt.FdtScan(out_fname)
246                 output_fdt_info[etype] = [other_dtb, out_fname]
247
248
249 def PrepareFromLoadedData(image):
250     """Get device tree files ready for use with a loaded image
251
252     Loaded images are different from images that are being created by binman,
253     since there is generally already an fdtmap and we read the description from
254     that. This provides the position and size of every entry in the image with
255     no calculation required.
256
257     This function uses the same output_fdt_info[] as Prepare(). It finds the
258     device tree files, adds a reference to the fdtmap and sets the FDT path
259     prefix to translate from the fdtmap (where the root node is the image node)
260     to the normal device tree (where the image node is under a /binman node).
261
262     Args:
263         images: List of images being used
264     """
265     global output_fdt_info, main_dtb, fdt_path_prefix
266
267     tout.info('Preparing device trees')
268     output_fdt_info.clear()
269     fdt_path_prefix = ''
270     output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb']
271     main_dtb = None
272     tout.info("   Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
273     for etype, value in image.GetFdts().items():
274         entry, fname = value
275         out_fname = tools.get_output_filename('%s.dtb' % entry.etype)
276         tout.info("   Found device tree type '%s' at '%s' path '%s'" %
277                   (etype, out_fname, entry.GetPath()))
278         entry._filename = entry.GetDefaultFilename()
279         data = entry.ReadData()
280
281         tools.write_file(out_fname, data)
282         dtb = fdt.Fdt(out_fname)
283         dtb.Scan()
284         image_node = dtb.GetNode('/binman')
285         if 'multiple-images' in image_node.props:
286             image_node = dtb.GetNode('/binman/%s' % image.image_node)
287         fdt_path_prefix = image_node.path
288         output_fdt_info[etype] = [dtb, None]
289     tout.info("   FDT path prefix '%s'" % fdt_path_prefix)
290
291
292 def GetAllFdts():
293     """Yield all device tree files being used by binman
294
295     Yields:
296         Device trees being used (U-Boot proper, SPL, TPL, VPL)
297     """
298     if main_dtb:
299         yield main_dtb
300     for etype in output_fdt_info:
301         dtb = output_fdt_info[etype][0]
302         if dtb != main_dtb:
303             yield dtb
304
305 def GetUpdateNodes(node, for_repack=False):
306     """Yield all the nodes that need to be updated in all device trees
307
308     The property referenced by this node is added to any device trees which
309     have the given node. Due to removable of unwanted nodes, SPL and TPL may
310     not have this node.
311
312     Args:
313         node: Node object in the main device tree to look up
314         for_repack: True if we want only nodes which need 'repack' properties
315             added to them (e.g. 'orig-offset'), False to return all nodes. We
316             don't add repack properties to SPL/TPL device trees.
317
318     Yields:
319         Node objects in each device tree that is in use (U-Boot proper, which
320             is node, SPL and TPL)
321     """
322     yield node
323     for entry_type, (dtb, fname) in output_fdt_info.items():
324         if dtb != node.GetFdt():
325             if for_repack and entry_type != 'u-boot-dtb':
326                 continue
327             other_node = dtb.GetNode(fdt_path_prefix + node.path)
328             if other_node:
329                 yield other_node
330
331 def AddZeroProp(node, prop, for_repack=False):
332     """Add a new property to affected device trees with an integer value of 0.
333
334     Args:
335         prop_name: Name of property
336         for_repack: True is this property is only needed for repacking
337     """
338     for n in GetUpdateNodes(node, for_repack):
339         n.AddZeroProp(prop)
340
341 def AddSubnode(node, name):
342     """Add a new subnode to a node in affected device trees
343
344     Args:
345         node: Node to add to
346         name: name of node to add
347
348     Returns:
349         New subnode that was created in main tree
350     """
351     first = None
352     for n in GetUpdateNodes(node):
353         subnode = n.AddSubnode(name)
354         if not first:
355             first = subnode
356     return first
357
358 def AddString(node, prop, value):
359     """Add a new string property to affected device trees
360
361     Args:
362         prop_name: Name of property
363         value: String value (which will be \0-terminated in the DT)
364     """
365     for n in GetUpdateNodes(node):
366         n.AddString(prop, value)
367
368 def AddInt(node, prop, value):
369     """Add a new string property to affected device trees
370
371     Args:
372         prop_name: Name of property
373         val: Integer value of property
374     """
375     for n in GetUpdateNodes(node):
376         n.AddInt(prop, value)
377
378 def SetInt(node, prop, value, for_repack=False):
379     """Update an integer property in affected device trees with an integer value
380
381     This is not allowed to change the size of the FDT.
382
383     Args:
384         prop_name: Name of property
385         for_repack: True is this property is only needed for repacking
386     """
387     for n in GetUpdateNodes(node, for_repack):
388         tout.debug("File %s: Update node '%s' prop '%s' to %#x" %
389                    (n.GetFdt().name, n.path, prop, value))
390         n.SetInt(prop, value)
391
392 def CheckAddHashProp(node):
393     hash_node = node.FindNode('hash')
394     if hash_node:
395         algo = hash_node.props.get('algo')
396         if not algo:
397             return "Missing 'algo' property for hash node"
398         if algo.value == 'sha256':
399             size = 32
400         else:
401             return "Unknown hash algorithm '%s'" % algo.value
402         for n in GetUpdateNodes(hash_node):
403             n.AddEmptyProp('value', size)
404
405 def CheckSetHashValue(node, get_data_func):
406     hash_node = node.FindNode('hash')
407     if hash_node:
408         algo = hash_node.props.get('algo').value
409         if algo == 'sha256':
410             m = hashlib.sha256()
411             m.update(get_data_func())
412             data = m.digest()
413         for n in GetUpdateNodes(hash_node):
414             n.SetData('value', data)
415
416 def SetAllowEntryExpansion(allow):
417     """Set whether post-pack expansion of entries is allowed
418
419     Args:
420        allow: True to allow expansion, False to raise an exception
421     """
422     global allow_entry_expansion
423
424     allow_entry_expansion = allow
425
426 def AllowEntryExpansion():
427     """Check whether post-pack expansion of entries is allowed
428
429     Returns:
430         True if expansion should be allowed, False if an exception should be
431             raised
432     """
433     return allow_entry_expansion
434
435 def SetAllowEntryContraction(allow):
436     """Set whether post-pack contraction of entries is allowed
437
438     Args:
439        allow: True to allow contraction, False to raise an exception
440     """
441     global allow_entry_contraction
442
443     allow_entry_contraction = allow
444
445 def AllowEntryContraction():
446     """Check whether post-pack contraction of entries is allowed
447
448     Returns:
449         True if contraction should be allowed, False if an exception should be
450             raised
451     """
452     return allow_entry_contraction
453
454 def SetThreads(threads):
455     """Set the number of threads to use when building sections
456
457     Args:
458         threads: Number of threads to use (None for default, 0 for
459             single-threaded)
460     """
461     global num_threads
462
463     num_threads = threads
464
465 def GetThreads():
466     """Get the number of threads to use when building sections
467
468     Returns:
469         Number of threads to use (None for default, 0 for single-threaded)
470     """
471     return num_threads
472
473 def GetTiming(name):
474     """Get the timing info for a particular operation
475
476     The object is created if it does not already exist.
477
478     Args:
479         name: Operation name to get
480
481     Returns:
482         Timing object for the current thread
483     """
484     threaded_name = '%s:%d' % (name, threading.get_ident())
485     timing = timing_info.get(threaded_name)
486     if not timing:
487         timing = Timing(threaded_name)
488         timing_info[threaded_name] = timing
489     return timing
490
491 def TimingStart(name):
492     """Start the timer for an operation
493
494     Args:
495         name: Operation name to start
496     """
497     timing = GetTiming(name)
498     timing.start = time.monotonic()
499
500 def TimingAccum(name):
501     """Stop and accumlate the time for an operation
502
503     This measures the time since the last TimingStart() and adds that to the
504     accumulated time.
505
506     Args:
507         name: Operation name to start
508     """
509     timing = GetTiming(name)
510     timing.accum += time.monotonic() - timing.start
511
512 def TimingShow():
513     """Show all timing information"""
514     duration = defaultdict(float)
515     for threaded_name, timing in timing_info.items():
516         name = threaded_name.split(':')[0]
517         duration[name] += timing.accum
518
519     for name, seconds in duration.items():
520         print('%10s: %10.1fms' % (name, seconds * 1000))
521
522 def GetVersion(path=OUR_PATH):
523     """Get the version string for binman
524
525     Args:
526         path: Path to 'version' file
527
528     Returns:
529         str: String version, e.g. 'v2021.10'
530     """
531     version_fname = os.path.join(path, 'version')
532     if os.path.exists(version_fname):
533         version = tools.read_file(version_fname, binary=False)
534     else:
535         version = '(unreleased)'
536     return version
This page took 0.052963 seconds and 4 git commands to generate.