state.py 16 KB

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