blob: 9e5b8a393103b4a667e2c73ee294602846d89d98 [file] [log] [blame]
Simon Glass29aa7362018-09-14 04:57:19 -06001# 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
Simon Glass748a1d42021-07-06 10:36:41 -06008from collections import defaultdict
Simon Glassae7cf032018-09-14 04:57:31 -06009import hashlib
Simon Glass29aa7362018-09-14 04:57:19 -060010import re
Simon Glass748a1d42021-07-06 10:36:41 -060011import time
Simon Glass76f496d2021-07-06 10:36:37 -060012import threading
Simon Glass29aa7362018-09-14 04:57:19 -060013
Simon Glassc585dd42020-04-17 18:09:03 -060014from dtoc import fdt
Simon Glass29aa7362018-09-14 04:57:19 -060015import os
Simon Glassa997ea52020-04-17 18:09:04 -060016from patman import tools
17from patman import tout
Simon Glass29aa7362018-09-14 04:57:19 -060018
Simon Glassf184d952021-03-18 20:25:00 +130019# Map an dtb etype to its expected filename
20DTB_TYPE_FNAME = {
21 'u-boot-spl-dtb': 'spl/u-boot-spl.dtb',
22 'u-boot-tpl-dtb': 'tpl/u-boot-tpl.dtb',
23 }
24
Simon Glass2be381d2019-07-20 12:23:32 -060025# Records the device-tree files known to binman, keyed by entry type (e.g.
26# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
27# binman. They have been copied to <xxx>.out files.
28#
Simon Glassa1ba6182021-03-18 20:25:01 +130029# key: entry type (e.g. 'u-boot-dtb)
Simon Glass2be381d2019-07-20 12:23:32 -060030# value: tuple:
31# Fdt object
32# Filename
Simon Glass95f1a942019-07-20 12:23:43 -060033output_fdt_info = {}
Simon Glass29aa7362018-09-14 04:57:19 -060034
Simon Glass072959a2019-07-20 12:23:50 -060035# Prefix to add to an fdtmap path to turn it into a path to the /binman node
36fdt_path_prefix = ''
37
Simon Glass29aa7362018-09-14 04:57:19 -060038# Arguments passed to binman to provide arguments to entries
39entry_args = {}
40
Simon Glass0c9d5b52018-09-14 04:57:22 -060041# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
42# ftest.py)
Simon Glass31402012018-09-14 04:57:23 -060043use_fake_dtb = False
Simon Glass0c9d5b52018-09-14 04:57:22 -060044
Simon Glassbdb40312018-09-14 04:57:20 -060045# The DTB which contains the full image information
46main_dtb = None
47
Simon Glasscbc80e82019-07-08 14:25:36 -060048# Allow entries to expand after they have been packed. This is detected and
49# forces a re-pack. If not allowed, any attempted expansion causes an error in
50# Entry.ProcessContentsUpdate()
51allow_entry_expansion = True
52
Simon Glass9d8ee322019-07-20 12:23:58 -060053# Don't allow entries to contract after they have been packed. Instead just
54# leave some wasted space. If allowed, this is detected and forces a re-pack,
55# but may result in entries that oscillate in size, thus causing a pack error.
56# An example is a compressed device tree where the original offset values
57# result in a larger compressed size than the new ones, but then after updating
58# to the new ones, the compressed size increases, etc.
59allow_entry_contraction = False
60
Simon Glass76f496d2021-07-06 10:36:37 -060061# Number of threads to use for binman (None means machine-dependent)
62num_threads = None
63
Simon Glass748a1d42021-07-06 10:36:41 -060064
65class Timing:
66 """Holds information about an operation that is being timed
67
68 Properties:
69 name: Operation name (only one of each name is stored)
70 start: Start time of operation in seconds (None if not start)
71 accum:: Amount of time spent on this operation so far, in seconds
72 """
73 def __init__(self, name):
74 self.name = name
75 self.start = None # cause an error if TimingStart() is not called
76 self.accum = 0.0
77
78
79# Holds timing info for each name:
80# key: name of Timing info (Timing.name)
81# value: Timing object
82timing_info = {}
83
84
Simon Glass2be381d2019-07-20 12:23:32 -060085def GetFdtForEtype(etype):
86 """Get the Fdt object for a particular device-tree entry
Simon Glass29aa7362018-09-14 04:57:19 -060087
88 Binman keeps track of at least one device-tree file called u-boot.dtb but
89 can also have others (e.g. for SPL). This function looks up the given
Simon Glass2be381d2019-07-20 12:23:32 -060090 entry and returns the associated Fdt object.
Simon Glass29aa7362018-09-14 04:57:19 -060091
92 Args:
Simon Glass2be381d2019-07-20 12:23:32 -060093 etype: Entry type of device tree (e.g. 'u-boot-dtb')
Simon Glass29aa7362018-09-14 04:57:19 -060094
95 Returns:
Simon Glass2be381d2019-07-20 12:23:32 -060096 Fdt object associated with the entry type
Simon Glass29aa7362018-09-14 04:57:19 -060097 """
Simon Glass95f1a942019-07-20 12:23:43 -060098 value = output_fdt_info.get(etype);
Simon Glass385138a2019-07-20 12:23:42 -060099 if not value:
100 return None
101 return value[0]
Simon Glass29aa7362018-09-14 04:57:19 -0600102
Simon Glass2be381d2019-07-20 12:23:32 -0600103def GetFdtPath(etype):
Simon Glass29aa7362018-09-14 04:57:19 -0600104 """Get the full pathname of a particular Fdt object
105
Simon Glassa3030a52019-07-20 12:23:30 -0600106 Similar to GetFdtForEtype() but returns the pathname associated with the
107 Fdt.
Simon Glass29aa7362018-09-14 04:57:19 -0600108
109 Args:
Simon Glass2be381d2019-07-20 12:23:32 -0600110 etype: Entry type of device tree (e.g. 'u-boot-dtb')
Simon Glass29aa7362018-09-14 04:57:19 -0600111
112 Returns:
113 Full path name to the associated Fdt
114 """
Simon Glass95f1a942019-07-20 12:23:43 -0600115 return output_fdt_info[etype][0]._fname
Simon Glass29aa7362018-09-14 04:57:19 -0600116
Simon Glass2be381d2019-07-20 12:23:32 -0600117def GetFdtContents(etype='u-boot-dtb'):
Simon Glasse219aa42018-09-14 04:57:24 -0600118 """Looks up the FDT pathname and contents
119
120 This is used to obtain the Fdt pathname and contents when needed by an
121 entry. It supports a 'fake' dtb, allowing tests to substitute test data for
122 the real dtb.
123
124 Args:
Simon Glass2be381d2019-07-20 12:23:32 -0600125 etype: Entry type to look up (e.g. 'u-boot.dtb').
Simon Glasse219aa42018-09-14 04:57:24 -0600126
127 Returns:
128 tuple:
129 pathname to Fdt
130 Fdt data (as bytes)
131 """
Simon Glass95f1a942019-07-20 12:23:43 -0600132 if etype not in output_fdt_info:
Simon Glass385138a2019-07-20 12:23:42 -0600133 return None, None
134 if not use_fake_dtb:
Simon Glass2be381d2019-07-20 12:23:32 -0600135 pathname = GetFdtPath(etype)
136 data = GetFdtForEtype(etype).GetContents()
Simon Glasse219aa42018-09-14 04:57:24 -0600137 else:
Simon Glass95f1a942019-07-20 12:23:43 -0600138 fname = output_fdt_info[etype][1]
Simon Glasse219aa42018-09-14 04:57:24 -0600139 pathname = tools.GetInputFilename(fname)
140 data = tools.ReadFile(pathname)
141 return pathname, data
142
Simon Glass74f5feb2019-07-20 12:24:08 -0600143def UpdateFdtContents(etype, data):
144 """Update the contents of a particular device tree
145
146 The device tree is updated and written back to its file. This affects what
147 is returned from future called to GetFdtContents(), etc.
148
149 Args:
150 etype: Entry type (e.g. 'u-boot-dtb')
151 data: Data to replace the DTB with
152 """
Simon Glassa1ba6182021-03-18 20:25:01 +1300153 dtb, fname = output_fdt_info[etype]
Simon Glass74f5feb2019-07-20 12:24:08 -0600154 dtb_fname = dtb.GetFilename()
155 tools.WriteFile(dtb_fname, data)
156 dtb = fdt.FdtScan(dtb_fname)
Simon Glassa1ba6182021-03-18 20:25:01 +1300157 output_fdt_info[etype] = [dtb, fname]
Simon Glass74f5feb2019-07-20 12:24:08 -0600158
Simon Glass29aa7362018-09-14 04:57:19 -0600159def SetEntryArgs(args):
160 """Set the value of the entry args
161
162 This sets up the entry_args dict which is used to supply entry arguments to
163 entries.
164
165 Args:
166 args: List of entry arguments, each in the format "name=value"
167 """
168 global entry_args
169
170 entry_args = {}
Simon Glass718b5292021-03-18 20:25:07 +1300171 tout.Debug('Processing entry args:')
Simon Glass29aa7362018-09-14 04:57:19 -0600172 if args:
173 for arg in args:
174 m = re.match('([^=]*)=(.*)', arg)
175 if not m:
176 raise ValueError("Invalid entry arguemnt '%s'" % arg)
Simon Glass718b5292021-03-18 20:25:07 +1300177 name, value = m.groups()
178 tout.Debug(' %20s = %s' % (name, value))
179 entry_args[name] = value
180 tout.Debug('Processing entry args done')
Simon Glass29aa7362018-09-14 04:57:19 -0600181
182def GetEntryArg(name):
183 """Get the value of an entry argument
184
185 Args:
186 name: Name of argument to retrieve
187
188 Returns:
189 String value of argument
190 """
191 return entry_args.get(name)
Simon Glassbdb40312018-09-14 04:57:20 -0600192
Simon Glass718b5292021-03-18 20:25:07 +1300193def GetEntryArgBool(name):
194 """Get the value of an entry argument as a boolean
195
196 Args:
197 name: Name of argument to retrieve
198
199 Returns:
200 False if the entry argument is consider False (empty, '0' or 'n'), else
201 True
202 """
203 val = GetEntryArg(name)
204 return val and val not in ['n', '0']
205
Simon Glass0c9d5b52018-09-14 04:57:22 -0600206def Prepare(images, dtb):
Simon Glassbdb40312018-09-14 04:57:20 -0600207 """Get device tree files ready for use
208
Simon Glass5a300602019-07-20 12:23:29 -0600209 This sets up a set of device tree files that can be retrieved by
210 GetAllFdts(). This includes U-Boot proper and any SPL device trees.
Simon Glassbdb40312018-09-14 04:57:20 -0600211
212 Args:
Simon Glass0c9d5b52018-09-14 04:57:22 -0600213 images: List of images being used
Simon Glassbdb40312018-09-14 04:57:20 -0600214 dtb: Main dtb
215 """
Simon Glass072959a2019-07-20 12:23:50 -0600216 global output_fdt_info, main_dtb, fdt_path_prefix
Simon Glassbdb40312018-09-14 04:57:20 -0600217 # Import these here in case libfdt.py is not available, in which case
218 # the above help option still works.
Simon Glassc585dd42020-04-17 18:09:03 -0600219 from dtoc import fdt
220 from dtoc import fdt_util
Simon Glassbdb40312018-09-14 04:57:20 -0600221
222 # If we are updating the DTBs we need to put these updated versions
223 # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
224 # since it is assumed to be the one passed in with options.dt, and
225 # was handled just above.
226 main_dtb = dtb
Simon Glass95f1a942019-07-20 12:23:43 -0600227 output_fdt_info.clear()
Simon Glass072959a2019-07-20 12:23:50 -0600228 fdt_path_prefix = ''
Simon Glassa1ba6182021-03-18 20:25:01 +1300229 output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb']
Simon Glassf184d952021-03-18 20:25:00 +1300230 if use_fake_dtb:
231 for etype, fname in DTB_TYPE_FNAME.items():
Simon Glassa1ba6182021-03-18 20:25:01 +1300232 output_fdt_info[etype] = [dtb, fname]
Simon Glassf184d952021-03-18 20:25:00 +1300233 else:
Simon Glassf3aba912019-07-20 12:23:34 -0600234 fdt_set = {}
Simon Glasseb751fd2021-03-18 20:25:03 +1300235 for etype, fname in DTB_TYPE_FNAME.items():
236 infile = tools.GetInputFilename(fname, allow_missing=True)
237 if infile and os.path.exists(infile):
238 fname_dtb = fdt_util.EnsureCompiled(infile)
239 out_fname = tools.GetOutputFilename('%s.out' %
240 os.path.split(fname)[1])
241 tools.WriteFile(out_fname, tools.ReadFile(fname_dtb))
242 other_dtb = fdt.FdtScan(out_fname)
243 output_fdt_info[etype] = [other_dtb, out_fname]
244
Simon Glassbdb40312018-09-14 04:57:20 -0600245
Simon Glass072959a2019-07-20 12:23:50 -0600246def PrepareFromLoadedData(image):
247 """Get device tree files ready for use with a loaded image
248
249 Loaded images are different from images that are being created by binman,
250 since there is generally already an fdtmap and we read the description from
251 that. This provides the position and size of every entry in the image with
252 no calculation required.
253
254 This function uses the same output_fdt_info[] as Prepare(). It finds the
255 device tree files, adds a reference to the fdtmap and sets the FDT path
256 prefix to translate from the fdtmap (where the root node is the image node)
257 to the normal device tree (where the image node is under a /binman node).
258
259 Args:
260 images: List of images being used
261 """
262 global output_fdt_info, main_dtb, fdt_path_prefix
263
264 tout.Info('Preparing device trees')
265 output_fdt_info.clear()
266 fdt_path_prefix = ''
Simon Glassa1ba6182021-03-18 20:25:01 +1300267 output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb']
Simon Glass072959a2019-07-20 12:23:50 -0600268 main_dtb = None
269 tout.Info(" Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
270 for etype, value in image.GetFdts().items():
271 entry, fname = value
272 out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype)
273 tout.Info(" Found device tree type '%s' at '%s' path '%s'" %
274 (etype, out_fname, entry.GetPath()))
275 entry._filename = entry.GetDefaultFilename()
276 data = entry.ReadData()
277
278 tools.WriteFile(out_fname, data)
279 dtb = fdt.Fdt(out_fname)
280 dtb.Scan()
281 image_node = dtb.GetNode('/binman')
282 if 'multiple-images' in image_node.props:
283 image_node = dtb.GetNode('/binman/%s' % image.image_node)
284 fdt_path_prefix = image_node.path
Simon Glassa1ba6182021-03-18 20:25:01 +1300285 output_fdt_info[etype] = [dtb, None]
Simon Glass072959a2019-07-20 12:23:50 -0600286 tout.Info(" FDT path prefix '%s'" % fdt_path_prefix)
287
288
Simon Glass5a300602019-07-20 12:23:29 -0600289def GetAllFdts():
Simon Glassbdb40312018-09-14 04:57:20 -0600290 """Yield all device tree files being used by binman
291
292 Yields:
293 Device trees being used (U-Boot proper, SPL, TPL)
294 """
Simon Glass072959a2019-07-20 12:23:50 -0600295 if main_dtb:
296 yield main_dtb
Simon Glass95f1a942019-07-20 12:23:43 -0600297 for etype in output_fdt_info:
298 dtb = output_fdt_info[etype][0]
Simon Glass132fa722019-07-20 12:23:33 -0600299 if dtb != main_dtb:
300 yield dtb
Simon Glassbdb40312018-09-14 04:57:20 -0600301
Simon Glassfb30e292019-07-20 12:23:51 -0600302def GetUpdateNodes(node, for_repack=False):
Simon Glassc8135dc2018-09-14 04:57:21 -0600303 """Yield all the nodes that need to be updated in all device trees
304
305 The property referenced by this node is added to any device trees which
306 have the given node. Due to removable of unwanted notes, SPL and TPL may
307 not have this node.
308
309 Args:
310 node: Node object in the main device tree to look up
Simon Glassfb30e292019-07-20 12:23:51 -0600311 for_repack: True if we want only nodes which need 'repack' properties
312 added to them (e.g. 'orig-offset'), False to return all nodes. We
313 don't add repack properties to SPL/TPL device trees.
Simon Glassc8135dc2018-09-14 04:57:21 -0600314
315 Yields:
316 Node objects in each device tree that is in use (U-Boot proper, which
317 is node, SPL and TPL)
318 """
319 yield node
Simon Glassa1ba6182021-03-18 20:25:01 +1300320 for entry_type, (dtb, fname) in output_fdt_info.items():
Simon Glasse219aa42018-09-14 04:57:24 -0600321 if dtb != node.GetFdt():
Simon Glassa1ba6182021-03-18 20:25:01 +1300322 if for_repack and entry_type != 'u-boot-dtb':
Simon Glassfb30e292019-07-20 12:23:51 -0600323 continue
Simon Glass072959a2019-07-20 12:23:50 -0600324 other_node = dtb.GetNode(fdt_path_prefix + node.path)
Simon Glasse219aa42018-09-14 04:57:24 -0600325 if other_node:
326 yield other_node
Simon Glassc8135dc2018-09-14 04:57:21 -0600327
Simon Glassfb30e292019-07-20 12:23:51 -0600328def AddZeroProp(node, prop, for_repack=False):
Simon Glassc8135dc2018-09-14 04:57:21 -0600329 """Add a new property to affected device trees with an integer value of 0.
330
331 Args:
332 prop_name: Name of property
Simon Glassfb30e292019-07-20 12:23:51 -0600333 for_repack: True is this property is only needed for repacking
Simon Glassc8135dc2018-09-14 04:57:21 -0600334 """
Simon Glassfb30e292019-07-20 12:23:51 -0600335 for n in GetUpdateNodes(node, for_repack):
Simon Glassc8135dc2018-09-14 04:57:21 -0600336 n.AddZeroProp(prop)
337
Simon Glassac6328c2018-09-14 04:57:28 -0600338def AddSubnode(node, name):
339 """Add a new subnode to a node in affected device trees
340
341 Args:
342 node: Node to add to
343 name: name of node to add
344
345 Returns:
346 New subnode that was created in main tree
347 """
348 first = None
349 for n in GetUpdateNodes(node):
350 subnode = n.AddSubnode(name)
351 if not first:
352 first = subnode
353 return first
354
355def AddString(node, prop, value):
356 """Add a new string property to affected device trees
357
358 Args:
359 prop_name: Name of property
360 value: String value (which will be \0-terminated in the DT)
361 """
362 for n in GetUpdateNodes(node):
363 n.AddString(prop, value)
364
Simon Glassa2af7302021-01-06 21:35:18 -0700365def AddInt(node, prop, value):
366 """Add a new string property to affected device trees
367
368 Args:
369 prop_name: Name of property
370 val: Integer value of property
371 """
372 for n in GetUpdateNodes(node):
373 n.AddInt(prop, value)
374
Simon Glassfb30e292019-07-20 12:23:51 -0600375def SetInt(node, prop, value, for_repack=False):
Simon Glassc8135dc2018-09-14 04:57:21 -0600376 """Update an integer property in affected device trees with an integer value
377
378 This is not allowed to change the size of the FDT.
379
380 Args:
381 prop_name: Name of property
Simon Glassfb30e292019-07-20 12:23:51 -0600382 for_repack: True is this property is only needed for repacking
Simon Glassc8135dc2018-09-14 04:57:21 -0600383 """
Simon Glassfb30e292019-07-20 12:23:51 -0600384 for n in GetUpdateNodes(node, for_repack):
385 tout.Detail("File %s: Update node '%s' prop '%s' to %#x" %
Simon Glassf8a54bc2019-07-20 12:23:56 -0600386 (n.GetFdt().name, n.path, prop, value))
Simon Glassc8135dc2018-09-14 04:57:21 -0600387 n.SetInt(prop, value)
Simon Glassae7cf032018-09-14 04:57:31 -0600388
389def CheckAddHashProp(node):
390 hash_node = node.FindNode('hash')
391 if hash_node:
392 algo = hash_node.props.get('algo')
393 if not algo:
394 return "Missing 'algo' property for hash node"
395 if algo.value == 'sha256':
396 size = 32
397 else:
398 return "Unknown hash algorithm '%s'" % algo
399 for n in GetUpdateNodes(hash_node):
400 n.AddEmptyProp('value', size)
401
402def CheckSetHashValue(node, get_data_func):
403 hash_node = node.FindNode('hash')
404 if hash_node:
405 algo = hash_node.props.get('algo').value
406 if algo == 'sha256':
407 m = hashlib.sha256()
408 m.update(get_data_func())
409 data = m.digest()
410 for n in GetUpdateNodes(hash_node):
411 n.SetData('value', data)
Simon Glasscbc80e82019-07-08 14:25:36 -0600412
413def SetAllowEntryExpansion(allow):
414 """Set whether post-pack expansion of entries is allowed
415
416 Args:
417 allow: True to allow expansion, False to raise an exception
418 """
419 global allow_entry_expansion
420
421 allow_entry_expansion = allow
422
423def AllowEntryExpansion():
424 """Check whether post-pack expansion of entries is allowed
425
426 Returns:
427 True if expansion should be allowed, False if an exception should be
428 raised
429 """
430 return allow_entry_expansion
Simon Glass9d8ee322019-07-20 12:23:58 -0600431
432def SetAllowEntryContraction(allow):
433 """Set whether post-pack contraction of entries is allowed
434
435 Args:
436 allow: True to allow contraction, False to raise an exception
437 """
438 global allow_entry_contraction
439
440 allow_entry_contraction = allow
441
442def AllowEntryContraction():
443 """Check whether post-pack contraction of entries is allowed
444
445 Returns:
446 True if contraction should be allowed, False if an exception should be
447 raised
448 """
449 return allow_entry_contraction
Simon Glass76f496d2021-07-06 10:36:37 -0600450
451def SetThreads(threads):
452 """Set the number of threads to use when building sections
453
454 Args:
455 threads: Number of threads to use (None for default, 0 for
456 single-threaded)
457 """
458 global num_threads
459
460 num_threads = threads
461
462def GetThreads():
463 """Get the number of threads to use when building sections
464
465 Returns:
466 Number of threads to use (None for default, 0 for single-threaded)
467 """
468 return num_threads
Simon Glass748a1d42021-07-06 10:36:41 -0600469
470def GetTiming(name):
471 """Get the timing info for a particular operation
472
473 The object is created if it does not already exist.
474
475 Args:
476 name: Operation name to get
477
478 Returns:
479 Timing object for the current thread
480 """
481 threaded_name = '%s:%d' % (name, threading.get_ident())
482 timing = timing_info.get(threaded_name)
483 if not timing:
484 timing = Timing(threaded_name)
485 timing_info[threaded_name] = timing
486 return timing
487
488def TimingStart(name):
489 """Start the timer for an operation
490
491 Args:
492 name: Operation name to start
493 """
494 timing = GetTiming(name)
495 timing.start = time.monotonic()
496
497def TimingAccum(name):
498 """Stop and accumlate the time for an operation
499
500 This measures the time since the last TimingStart() and adds that to the
501 accumulated time.
502
503 Args:
504 name: Operation name to start
505 """
506 timing = GetTiming(name)
507 timing.accum += time.monotonic() - timing.start
508
509def TimingShow():
510 """Show all timing information"""
511 duration = defaultdict(float)
512 for threaded_name, timing in timing_info.items():
513 name = threaded_name.split(':')[0]
514 duration[name] += timing.accum
515
516 for name, seconds in duration.items():
517 print('%10s: %10.1fms' % (name, seconds * 1000))