state.py 12 KB

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