state.py 16 KB

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