1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2018 Google, Inc
5 # Holds and modifies the state information held by binman
8 from collections import defaultdict
16 from u_boot_pylib import tools
17 from u_boot_pylib import tout
19 OUR_PATH = os.path.dirname(os.path.realpath(__file__))
21 # Map an dtb etype to its expected filename
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',
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.
32 # key: entry type (e.g. 'u-boot-dtb)
38 # Prefix to add to an fdtmap path to turn it into a path to the /binman node
41 # Arguments passed to binman to provide arguments to entries
44 # True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
48 # The DTB which contains the full image information
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
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
64 # Number of threads to use for binman (None means machine-dependent)
69 """Holds information about an operation that is being timed
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
76 def __init__(self, name):
78 self.start = None # cause an error if TimingStart() is not called
82 # Holds timing info for each name:
83 # key: name of Timing info (Timing.name)
84 # value: Timing object
88 def GetFdtForEtype(etype):
89 """Get the Fdt object for a particular device-tree entry
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.
96 etype: Entry type of device tree (e.g. 'u-boot-dtb')
99 Fdt object associated with the entry type
101 value = output_fdt_info.get(etype);
106 def GetFdtPath(etype):
107 """Get the full pathname of a particular Fdt object
109 Similar to GetFdtForEtype() but returns the pathname associated with the
113 etype: Entry type of device tree (e.g. 'u-boot-dtb')
116 Full path name to the associated Fdt
118 return output_fdt_info[etype][0]._fname
120 def GetFdtContents(etype='u-boot-dtb'):
121 """Looks up the FDT pathname and contents
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
128 etype: Entry type to look up (e.g. 'u-boot.dtb').
135 if etype not in output_fdt_info:
138 pathname = GetFdtPath(etype)
139 data = GetFdtForEtype(etype).GetContents()
141 fname = output_fdt_info[etype][1]
142 pathname = tools.get_input_filename(fname)
143 data = tools.read_file(pathname)
144 return pathname, data
146 def UpdateFdtContents(etype, data):
147 """Update the contents of a particular device tree
149 The device tree is updated and written back to its file. This affects what
150 is returned from future called to GetFdtContents(), etc.
153 etype: Entry type (e.g. 'u-boot-dtb')
154 data: Data to replace the DTB with
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]
162 def SetEntryArgs(args):
163 """Set the value of the entry args
165 This sets up the entry_args dict which is used to supply entry arguments to
169 args: List of entry arguments, each in the format "name=value"
174 tout.debug('Processing entry args:')
177 m = re.match('([^=]*)=(.*)', arg)
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')
185 def GetEntryArg(name):
186 """Get the value of an entry argument
189 name: Name of argument to retrieve
192 String value of argument
194 return entry_args.get(name)
196 def GetEntryArgBool(name):
197 """Get the value of an entry argument as a boolean
200 name: Name of argument to retrieve
203 False if the entry argument is consider False (empty, '0' or 'n'), else
206 val = GetEntryArg(name)
207 return val and val not in ['n', '0']
209 def Prepare(images, dtb):
210 """Get device tree files ready for use
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.
216 images: List of images being used
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.
223 from dtoc import fdt_util
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.
230 output_fdt_info.clear()
232 output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb']
234 for etype, fname in DTB_TYPE_FNAME.items():
235 output_fdt_info[etype] = [dtb, fname]
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]
249 def PrepareFromLoadedData(image):
250 """Get device tree files ready for use with a loaded image
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.
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).
263 images: List of images being used
265 global output_fdt_info, main_dtb, fdt_path_prefix
267 tout.info('Preparing device trees')
268 output_fdt_info.clear()
270 output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb']
272 tout.info(" Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
273 for etype, value in image.GetFdts().items():
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()
281 tools.write_file(out_fname, data)
282 dtb = fdt.Fdt(out_fname)
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)
293 """Yield all device tree files being used by binman
296 Device trees being used (U-Boot proper, SPL, TPL, VPL)
300 for etype in output_fdt_info:
301 dtb = output_fdt_info[etype][0]
305 def GetUpdateNodes(node, for_repack=False):
306 """Yield all the nodes that need to be updated in all device trees
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
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.
319 Node objects in each device tree that is in use (U-Boot proper, which
320 is node, SPL and TPL)
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':
327 other_node = dtb.GetNode(fdt_path_prefix + node.path)
331 def AddZeroProp(node, prop, for_repack=False):
332 """Add a new property to affected device trees with an integer value of 0.
335 prop_name: Name of property
336 for_repack: True is this property is only needed for repacking
338 for n in GetUpdateNodes(node, for_repack):
341 def AddSubnode(node, name):
342 """Add a new subnode to a node in affected device trees
346 name: name of node to add
349 New subnode that was created in main tree
352 for n in GetUpdateNodes(node):
353 subnode = n.AddSubnode(name)
358 def AddString(node, prop, value):
359 """Add a new string property to affected device trees
362 prop_name: Name of property
363 value: String value (which will be \0-terminated in the DT)
365 for n in GetUpdateNodes(node):
366 n.AddString(prop, value)
368 def AddInt(node, prop, value):
369 """Add a new string property to affected device trees
372 prop_name: Name of property
373 val: Integer value of property
375 for n in GetUpdateNodes(node):
376 n.AddInt(prop, value)
378 def SetInt(node, prop, value, for_repack=False):
379 """Update an integer property in affected device trees with an integer value
381 This is not allowed to change the size of the FDT.
384 prop_name: Name of property
385 for_repack: True is this property is only needed for repacking
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)
392 def CheckAddHashProp(node):
393 hash_node = node.FindNode('hash')
395 algo = hash_node.props.get('algo')
397 return "Missing 'algo' property for hash node"
398 if algo.value == 'sha256':
401 return "Unknown hash algorithm '%s'" % algo.value
402 for n in GetUpdateNodes(hash_node):
403 n.AddEmptyProp('value', size)
405 def CheckSetHashValue(node, get_data_func):
406 hash_node = node.FindNode('hash')
408 algo = hash_node.props.get('algo').value
411 m.update(get_data_func())
413 for n in GetUpdateNodes(hash_node):
414 n.SetData('value', data)
416 def SetAllowEntryExpansion(allow):
417 """Set whether post-pack expansion of entries is allowed
420 allow: True to allow expansion, False to raise an exception
422 global allow_entry_expansion
424 allow_entry_expansion = allow
426 def AllowEntryExpansion():
427 """Check whether post-pack expansion of entries is allowed
430 True if expansion should be allowed, False if an exception should be
433 return allow_entry_expansion
435 def SetAllowEntryContraction(allow):
436 """Set whether post-pack contraction of entries is allowed
439 allow: True to allow contraction, False to raise an exception
441 global allow_entry_contraction
443 allow_entry_contraction = allow
445 def AllowEntryContraction():
446 """Check whether post-pack contraction of entries is allowed
449 True if contraction should be allowed, False if an exception should be
452 return allow_entry_contraction
454 def SetThreads(threads):
455 """Set the number of threads to use when building sections
458 threads: Number of threads to use (None for default, 0 for
463 num_threads = threads
466 """Get the number of threads to use when building sections
469 Number of threads to use (None for default, 0 for single-threaded)
474 """Get the timing info for a particular operation
476 The object is created if it does not already exist.
479 name: Operation name to get
482 Timing object for the current thread
484 threaded_name = '%s:%d' % (name, threading.get_ident())
485 timing = timing_info.get(threaded_name)
487 timing = Timing(threaded_name)
488 timing_info[threaded_name] = timing
491 def TimingStart(name):
492 """Start the timer for an operation
495 name: Operation name to start
497 timing = GetTiming(name)
498 timing.start = time.monotonic()
500 def TimingAccum(name):
501 """Stop and accumlate the time for an operation
503 This measures the time since the last TimingStart() and adds that to the
507 name: Operation name to start
509 timing = GetTiming(name)
510 timing.accum += time.monotonic() - timing.start
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
519 for name, seconds in duration.items():
520 print('%10s: %10.1fms' % (name, seconds * 1000))
522 def GetVersion(path=OUR_PATH):
523 """Get the version string for binman
526 path: Path to 'version' file
529 str: String version, e.g. 'v2021.10'
531 version_fname = os.path.join(path, 'version')
532 if os.path.exists(version_fname):
533 version = tools.read_file(version_fname, binary=False)
535 version = '(unreleased)'