state.py 14 KB

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