entry.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384
  1. # SPDX-License-Identifier: GPL-2.0+
  2. # Copyright (c) 2016 Google, Inc
  3. #
  4. # Base class for all entries
  5. #
  6. from collections import namedtuple
  7. import importlib
  8. import os
  9. import pathlib
  10. import sys
  11. import time
  12. from binman import bintool
  13. from binman import elf
  14. from dtoc import fdt_util
  15. from u_boot_pylib import tools
  16. from u_boot_pylib.tools import to_hex, to_hex_size
  17. from u_boot_pylib import tout
  18. modules = {}
  19. # This is imported if needed
  20. state = None
  21. # An argument which can be passed to entries on the command line, in lieu of
  22. # device-tree properties.
  23. EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
  24. # Information about an entry for use when displaying summaries
  25. EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
  26. 'image_pos', 'uncomp_size', 'offset',
  27. 'entry'])
  28. class Entry(object):
  29. """An Entry in the section
  30. An entry corresponds to a single node in the device-tree description
  31. of the section. Each entry ends up being a part of the final section.
  32. Entries can be placed either right next to each other, or with padding
  33. between them. The type of the entry determines the data that is in it.
  34. This class is not used by itself. All entry objects are subclasses of
  35. Entry.
  36. Attributes:
  37. section: Section object containing this entry
  38. node: The node that created this entry
  39. offset: Offset of entry within the section, None if not known yet (in
  40. which case it will be calculated by Pack())
  41. size: Entry size in bytes, None if not known
  42. min_size: Minimum entry size in bytes
  43. pre_reset_size: size as it was before ResetForPack(). This allows us to
  44. keep track of the size we started with and detect size changes
  45. uncomp_size: Size of uncompressed data in bytes, if the entry is
  46. compressed, else None
  47. contents_size: Size of contents in bytes, 0 by default
  48. align: Entry start offset alignment relative to the start of the
  49. containing section, or None
  50. align_size: Entry size alignment, or None
  51. align_end: Entry end offset alignment relative to the start of the
  52. containing section, or None
  53. pad_before: Number of pad bytes before the contents when it is placed
  54. in the containing section, 0 if none. The pad bytes become part of
  55. the entry.
  56. pad_after: Number of pad bytes after the contents when it is placed in
  57. the containing section, 0 if none. The pad bytes become part of
  58. the entry.
  59. data: Contents of entry (string of bytes). This does not include
  60. padding created by pad_before or pad_after. If the entry is
  61. compressed, this contains the compressed data.
  62. uncomp_data: Original uncompressed data, if this entry is compressed,
  63. else None
  64. compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
  65. orig_offset: Original offset value read from node
  66. orig_size: Original size value read from node
  67. missing: True if this entry is missing its contents. Note that if it is
  68. optional, this entry will not appear in the list generated by
  69. entry.CheckMissing() since it is considered OK for it to be missing.
  70. allow_missing: Allow children of this entry to be missing (used by
  71. subclasses such as Entry_section)
  72. allow_fake: Allow creating a dummy fake file if the blob file is not
  73. available. This is mainly used for testing.
  74. external: True if this entry contains an external binary blob
  75. bintools: Bintools used by this entry (only populated for Image)
  76. missing_bintools: List of missing bintools for this entry
  77. update_hash: True if this entry's "hash" subnode should be
  78. updated with a hash of the entry contents
  79. comp_bintool: Bintools used for compress and decompress data
  80. fake_fname: Fake filename, if one was created, else None
  81. required_props (dict of str): Properties which must be present. This can
  82. be added to by subclasses
  83. elf_fname (str): Filename of the ELF file, if this entry holds an ELF
  84. file, or is a binary file produced from an ELF file
  85. auto_write_symbols (bool): True to write ELF symbols into this entry's
  86. contents
  87. absent (bool): True if this entry is absent. This can be controlled by
  88. the entry itself, allowing it to vanish in certain circumstances.
  89. An absent entry is removed during processing so that it does not
  90. appear in the map
  91. optional (bool): True if this entry contains an optional external blob
  92. overlap (bool): True if this entry overlaps with others
  93. preserve (bool): True if this entry should be preserved when updating
  94. firmware. This means that it will not be changed by the update.
  95. This is just a signal: enforcement of this is up to the updater.
  96. This flag does not automatically propagate down to child entries.
  97. build_done (bool): Indicates that the entry data has been built and does
  98. not need to be done again. This is only used with 'binman replace',
  99. to stop sections from being rebuilt if their entries have not been
  100. replaced
  101. """
  102. fake_dir = None
  103. def __init__(self, section, etype, node, name_prefix='',
  104. auto_write_symbols=False):
  105. # Put this here to allow entry-docs and help to work without libfdt
  106. global state
  107. from binman import state
  108. self.section = section
  109. self.etype = etype
  110. self._node = node
  111. self.name = node and (name_prefix + node.name) or 'none'
  112. self.offset = None
  113. self.size = None
  114. self.min_size = 0
  115. self.pre_reset_size = None
  116. self.uncomp_size = None
  117. self.data = None
  118. self.uncomp_data = None
  119. self.contents_size = 0
  120. self.align = None
  121. self.align_size = None
  122. self.align_end = None
  123. self.pad_before = 0
  124. self.pad_after = 0
  125. self.offset_unset = False
  126. self.image_pos = None
  127. self.extend_size = False
  128. self.compress = 'none'
  129. self.missing = False
  130. self.faked = False
  131. self.external = False
  132. self.allow_missing = False
  133. self.allow_fake = False
  134. self.bintools = {}
  135. self.missing_bintools = []
  136. self.update_hash = True
  137. self.fake_fname = None
  138. self.required_props = []
  139. self.comp_bintool = None
  140. self.elf_fname = None
  141. self.auto_write_symbols = auto_write_symbols
  142. self.absent = False
  143. self.optional = False
  144. self.overlap = False
  145. self.elf_base_sym = None
  146. self.offset_from_elf = None
  147. self.preserve = False
  148. self.build_done = False
  149. self.no_write_symbols = False
  150. @staticmethod
  151. def FindEntryClass(etype, expanded):
  152. """Look up the entry class for a node.
  153. Args:
  154. node_node: Path name of Node object containing information about
  155. the entry to create (used for errors)
  156. etype: Entry type to use
  157. expanded: Use the expanded version of etype
  158. Returns:
  159. The entry class object if found, else None if not found and expanded
  160. is True, else a tuple:
  161. module name that could not be found
  162. exception received
  163. """
  164. # Convert something like 'u-boot@0' to 'u_boot' since we are only
  165. # interested in the type.
  166. module_name = etype.replace('-', '_')
  167. if '@' in module_name:
  168. module_name = module_name.split('@')[0]
  169. if expanded:
  170. module_name += '_expanded'
  171. module = modules.get(module_name)
  172. # Also allow entry-type modules to be brought in from the etype directory.
  173. # Import the module if we have not already done so.
  174. if not module:
  175. try:
  176. module = importlib.import_module('binman.etype.' + module_name)
  177. except ImportError as e:
  178. if expanded:
  179. return None
  180. return module_name, e
  181. modules[module_name] = module
  182. # Look up the expected class name
  183. return getattr(module, 'Entry_%s' % module_name)
  184. @staticmethod
  185. def Lookup(node_path, etype, expanded, missing_etype=False):
  186. """Look up the entry class for a node.
  187. Args:
  188. node_node (str): Path name of Node object containing information
  189. about the entry to create (used for errors)
  190. etype (str): Entry type to use
  191. expanded (bool): Use the expanded version of etype
  192. missing_etype (bool): True to default to a blob etype if the
  193. requested etype is not found
  194. Returns:
  195. The entry class object if found, else None if not found and expanded
  196. is True
  197. Raise:
  198. ValueError if expanded is False and the class is not found
  199. """
  200. # Convert something like 'u-boot@0' to 'u_boot' since we are only
  201. # interested in the type.
  202. cls = Entry.FindEntryClass(etype, expanded)
  203. if cls is None:
  204. return None
  205. elif isinstance(cls, tuple):
  206. if missing_etype:
  207. cls = Entry.FindEntryClass('blob', False)
  208. if isinstance(cls, tuple): # This should not fail
  209. module_name, e = cls
  210. raise ValueError(
  211. "Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
  212. (etype, node_path, module_name, e))
  213. return cls
  214. @staticmethod
  215. def Create(section, node, etype=None, expanded=False, missing_etype=False):
  216. """Create a new entry for a node.
  217. Args:
  218. section (entry_Section): Section object containing this node
  219. node (Node): Node object containing information about the entry to
  220. create
  221. etype (str): Entry type to use, or None to work it out (used for
  222. tests)
  223. expanded (bool): Use the expanded version of etype
  224. missing_etype (bool): True to default to a blob etype if the
  225. requested etype is not found
  226. Returns:
  227. A new Entry object of the correct type (a subclass of Entry)
  228. """
  229. if not etype:
  230. etype = fdt_util.GetString(node, 'type', node.name)
  231. obj = Entry.Lookup(node.path, etype, expanded, missing_etype)
  232. if obj and expanded:
  233. # Check whether to use the expanded entry
  234. new_etype = etype + '-expanded'
  235. can_expand = not fdt_util.GetBool(node, 'no-expanded')
  236. if can_expand and obj.UseExpanded(node, etype, new_etype):
  237. etype = new_etype
  238. else:
  239. obj = None
  240. if not obj:
  241. obj = Entry.Lookup(node.path, etype, False, missing_etype)
  242. # Call its constructor to get the object we want.
  243. return obj(section, etype, node)
  244. def ReadNode(self):
  245. """Read entry information from the node
  246. This must be called as the first thing after the Entry is created.
  247. This reads all the fields we recognise from the node, ready for use.
  248. """
  249. self.ensure_props()
  250. if 'pos' in self._node.props:
  251. self.Raise("Please use 'offset' instead of 'pos'")
  252. if 'expand-size' in self._node.props:
  253. self.Raise("Please use 'extend-size' instead of 'expand-size'")
  254. self.offset = fdt_util.GetInt(self._node, 'offset')
  255. self.size = fdt_util.GetInt(self._node, 'size')
  256. self.min_size = fdt_util.GetInt(self._node, 'min-size', 0)
  257. self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
  258. self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
  259. if self.GetImage().copy_to_orig:
  260. self.orig_offset = self.offset
  261. self.orig_size = self.size
  262. # These should not be set in input files, but are set in an FDT map,
  263. # which is also read by this code.
  264. self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
  265. self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
  266. self.align = fdt_util.GetInt(self._node, 'align')
  267. if tools.not_power_of_two(self.align):
  268. raise ValueError("Node '%s': Alignment %s must be a power of two" %
  269. (self._node.path, self.align))
  270. if self.section and self.align is None:
  271. self.align = self.section.align_default
  272. self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
  273. self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
  274. self.align_size = fdt_util.GetInt(self._node, 'align-size')
  275. if tools.not_power_of_two(self.align_size):
  276. self.Raise("Alignment size %s must be a power of two" %
  277. self.align_size)
  278. self.align_end = fdt_util.GetInt(self._node, 'align-end')
  279. self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
  280. self.extend_size = fdt_util.GetBool(self._node, 'extend-size')
  281. self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
  282. self.optional = fdt_util.GetBool(self._node, 'optional')
  283. self.overlap = fdt_util.GetBool(self._node, 'overlap')
  284. if self.overlap:
  285. self.required_props += ['offset', 'size']
  286. # This is only supported by blobs and sections at present
  287. self.compress = fdt_util.GetString(self._node, 'compress', 'none')
  288. self.offset_from_elf = fdt_util.GetPhandleNameOffset(self._node,
  289. 'offset-from-elf')
  290. self.preserve = fdt_util.GetBool(self._node, 'preserve')
  291. self.no_write_symbols = fdt_util.GetBool(self._node, 'no-write-symbols')
  292. def GetDefaultFilename(self):
  293. return None
  294. def GetFdts(self):
  295. """Get the device trees used by this entry
  296. Returns:
  297. Empty dict, if this entry is not a .dtb, otherwise:
  298. Dict:
  299. key: Filename from this entry (without the path)
  300. value: Tuple:
  301. Entry object for this dtb
  302. Filename of file containing this dtb
  303. """
  304. return {}
  305. def gen_entries(self):
  306. """Allow entries to generate other entries
  307. Some entries generate subnodes automatically, from which sub-entries
  308. are then created. This method allows those to be added to the binman
  309. definition for the current image. An entry which implements this method
  310. should call state.AddSubnode() to add a subnode and can add properties
  311. with state.AddString(), etc.
  312. An example is 'files', which produces a section containing a list of
  313. files.
  314. """
  315. pass
  316. def AddMissingProperties(self, have_image_pos):
  317. """Add new properties to the device tree as needed for this entry
  318. Args:
  319. have_image_pos: True if this entry has an image position. This can
  320. be False if its parent section is compressed, since compression
  321. groups all entries together into a compressed block of data,
  322. obscuring the start of each individual child entry
  323. """
  324. for prop in ['offset', 'size']:
  325. if not prop in self._node.props:
  326. state.AddZeroProp(self._node, prop)
  327. if have_image_pos and 'image-pos' not in self._node.props:
  328. state.AddZeroProp(self._node, 'image-pos')
  329. if self.GetImage().allow_repack:
  330. if self.orig_offset is not None:
  331. state.AddZeroProp(self._node, 'orig-offset', True)
  332. if self.orig_size is not None:
  333. state.AddZeroProp(self._node, 'orig-size', True)
  334. if self.compress != 'none':
  335. state.AddZeroProp(self._node, 'uncomp-size')
  336. if self.update_hash:
  337. err = state.CheckAddHashProp(self._node)
  338. if err:
  339. self.Raise(err)
  340. def SetCalculatedProperties(self):
  341. """Set the value of device-tree properties calculated by binman"""
  342. state.SetInt(self._node, 'offset', self.offset)
  343. state.SetInt(self._node, 'size', self.size)
  344. base = self.section.GetRootSkipAtStart() if self.section else 0
  345. if self.image_pos is not None:
  346. state.SetInt(self._node, 'image-pos', self.image_pos - base)
  347. if self.GetImage().allow_repack:
  348. if self.orig_offset is not None:
  349. state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
  350. if self.orig_size is not None:
  351. state.SetInt(self._node, 'orig-size', self.orig_size, True)
  352. if self.uncomp_size is not None:
  353. state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
  354. if self.update_hash:
  355. state.CheckSetHashValue(self._node, self.GetData)
  356. def ProcessFdt(self, fdt):
  357. """Allow entries to adjust the device tree
  358. Some entries need to adjust the device tree for their purposes. This
  359. may involve adding or deleting properties.
  360. Returns:
  361. True if processing is complete
  362. False if processing could not be completed due to a dependency.
  363. This will cause the entry to be retried after others have been
  364. called
  365. """
  366. return True
  367. def SetPrefix(self, prefix):
  368. """Set the name prefix for a node
  369. Args:
  370. prefix: Prefix to set, or '' to not use a prefix
  371. """
  372. if prefix:
  373. self.name = prefix + self.name
  374. def SetContents(self, data):
  375. """Set the contents of an entry
  376. This sets both the data and content_size properties
  377. Args:
  378. data: Data to set to the contents (bytes)
  379. """
  380. self.data = data
  381. self.contents_size = len(self.data)
  382. def ProcessContentsUpdate(self, data):
  383. """Update the contents of an entry, after the size is fixed
  384. This checks that the new data is the same size as the old. If the size
  385. has changed, this triggers a re-run of the packing algorithm.
  386. Args:
  387. data: Data to set to the contents (bytes)
  388. Raises:
  389. ValueError if the new data size is not the same as the old
  390. """
  391. size_ok = True
  392. new_size = len(data)
  393. if state.AllowEntryExpansion() and new_size > self.contents_size:
  394. # self.data will indicate the new size needed
  395. size_ok = False
  396. elif state.AllowEntryContraction() and new_size < self.contents_size:
  397. size_ok = False
  398. # If not allowed to change, try to deal with it or give up
  399. if size_ok:
  400. if new_size > self.contents_size:
  401. self.Raise('Cannot update entry size from %d to %d' %
  402. (self.contents_size, new_size))
  403. # Don't let the data shrink. Pad it if necessary
  404. if size_ok and new_size < self.contents_size:
  405. data += tools.get_bytes(0, self.contents_size - new_size)
  406. if not size_ok:
  407. tout.debug("Entry '%s' size change from %s to %s" % (
  408. self._node.path, to_hex(self.contents_size),
  409. to_hex(new_size)))
  410. self.SetContents(data)
  411. return size_ok
  412. def ObtainContents(self, skip_entry=None, fake_size=0):
  413. """Figure out the contents of an entry.
  414. For missing blobs (where allow-missing is enabled), the contents are set
  415. to b'' and self.missing is set to True.
  416. Args:
  417. skip_entry (Entry): Entry to skip when obtaining section contents
  418. fake_size (int): Size of fake file to create if needed
  419. Returns:
  420. True if the contents were found, False if another call is needed
  421. after the other entries are processed, None if there is no contents
  422. """
  423. # No contents by default: subclasses can implement this
  424. return True
  425. def ResetForPack(self):
  426. """Reset offset/size fields so that packing can be done again"""
  427. self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
  428. (to_hex(self.offset), to_hex(self.orig_offset),
  429. to_hex(self.size), to_hex(self.orig_size)))
  430. self.pre_reset_size = self.size
  431. self.offset = self.orig_offset
  432. self.size = self.orig_size
  433. def Pack(self, offset):
  434. """Figure out how to pack the entry into the section
  435. Most of the time the entries are not fully specified. There may be
  436. an alignment but no size. In that case we take the size from the
  437. contents of the entry.
  438. If an entry has no hard-coded offset, it will be placed at @offset.
  439. Once this function is complete, both the offset and size of the
  440. entry will be know.
  441. Args:
  442. Current section offset pointer
  443. Returns:
  444. New section offset pointer (after this entry)
  445. """
  446. self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
  447. (to_hex(self.offset), to_hex(self.size),
  448. self.contents_size))
  449. if self.offset is None:
  450. if self.offset_unset:
  451. self.Raise('No offset set with offset-unset: should another '
  452. 'entry provide this correct offset?')
  453. elif self.offset_from_elf:
  454. self.offset = self.lookup_offset()
  455. else:
  456. self.offset = tools.align(offset, self.align)
  457. needed = self.pad_before + self.contents_size + self.pad_after
  458. needed = max(needed, self.min_size)
  459. needed = tools.align(needed, self.align_size)
  460. size = self.size
  461. if not size:
  462. size = needed
  463. new_offset = self.offset + size
  464. aligned_offset = tools.align(new_offset, self.align_end)
  465. if aligned_offset != new_offset:
  466. size = aligned_offset - self.offset
  467. new_offset = aligned_offset
  468. if not self.size:
  469. self.size = size
  470. if self.size < needed:
  471. self.Raise("Entry contents size is %#x (%d) but entry size is "
  472. "%#x (%d)" % (needed, needed, self.size, self.size))
  473. # Check that the alignment is correct. It could be wrong if the
  474. # and offset or size values were provided (i.e. not calculated), but
  475. # conflict with the provided alignment values
  476. if self.size != tools.align(self.size, self.align_size):
  477. self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
  478. (self.size, self.size, self.align_size, self.align_size))
  479. if self.offset != tools.align(self.offset, self.align):
  480. self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
  481. (self.offset, self.offset, self.align, self.align))
  482. self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
  483. (self.offset, self.size, self.contents_size, new_offset))
  484. return new_offset
  485. def Raise(self, msg):
  486. """Convenience function to raise an error referencing a node"""
  487. raise ValueError("Node '%s': %s" % (self._node.path, msg))
  488. def Info(self, msg):
  489. """Convenience function to log info referencing a node"""
  490. tag = "Info '%s'" % self._node.path
  491. tout.detail('%30s: %s' % (tag, msg))
  492. def Detail(self, msg):
  493. """Convenience function to log detail referencing a node"""
  494. tag = "Node '%s'" % self._node.path
  495. tout.detail('%30s: %s' % (tag, msg))
  496. def GetEntryArgsOrProps(self, props, required=False):
  497. """Return the values of a set of properties
  498. Args:
  499. props: List of EntryArg objects
  500. Raises:
  501. ValueError if a property is not found
  502. """
  503. values = []
  504. missing = []
  505. for prop in props:
  506. python_prop = prop.name.replace('-', '_')
  507. if hasattr(self, python_prop):
  508. value = getattr(self, python_prop)
  509. else:
  510. value = None
  511. if value is None:
  512. value = self.GetArg(prop.name, prop.datatype)
  513. if value is None and required:
  514. missing.append(prop.name)
  515. values.append(value)
  516. if missing:
  517. self.GetImage().MissingArgs(self, missing)
  518. return values
  519. def GetPath(self):
  520. """Get the path of a node
  521. Returns:
  522. Full path of the node for this entry
  523. """
  524. return self._node.path
  525. def GetData(self, required=True):
  526. """Get the contents of an entry
  527. Args:
  528. required: True if the data must be present, False if it is OK to
  529. return None
  530. Returns:
  531. bytes content of the entry, excluding any padding. If the entry is
  532. compressed, the compressed data is returned. If the entry data
  533. is not yet available, False can be returned. If the entry data
  534. is null, then None is returned.
  535. """
  536. self.Detail('GetData: size %s' % to_hex_size(self.data))
  537. return self.data
  538. def GetPaddedData(self, data=None):
  539. """Get the data for an entry including any padding
  540. Gets the entry data and uses its section's pad-byte value to add padding
  541. before and after as defined by the pad-before and pad-after properties.
  542. This does not consider alignment.
  543. Returns:
  544. Contents of the entry along with any pad bytes before and
  545. after it (bytes)
  546. """
  547. if data is None:
  548. data = self.GetData()
  549. return self.section.GetPaddedDataForEntry(self, data)
  550. def GetOffsets(self):
  551. """Get the offsets for siblings
  552. Some entry types can contain information about the position or size of
  553. other entries. An example of this is the Intel Flash Descriptor, which
  554. knows where the Intel Management Engine section should go.
  555. If this entry knows about the position of other entries, it can specify
  556. this by returning values here
  557. Returns:
  558. Dict:
  559. key: Entry type
  560. value: List containing position and size of the given entry
  561. type. Either can be None if not known
  562. """
  563. return {}
  564. def SetOffsetSize(self, offset, size):
  565. """Set the offset and/or size of an entry
  566. Args:
  567. offset: New offset, or None to leave alone
  568. size: New size, or None to leave alone
  569. """
  570. if offset is not None:
  571. self.offset = offset
  572. if size is not None:
  573. self.size = size
  574. def SetImagePos(self, image_pos):
  575. """Set the position in the image
  576. Args:
  577. image_pos: Position of this entry in the image
  578. """
  579. self.image_pos = image_pos + self.offset
  580. def ProcessContents(self):
  581. """Do any post-packing updates of entry contents
  582. This function should call ProcessContentsUpdate() to update the entry
  583. contents, if necessary, returning its return value here.
  584. Args:
  585. data: Data to set to the contents (bytes)
  586. Returns:
  587. True if the new data size is OK, False if expansion is needed
  588. Raises:
  589. ValueError if the new data size is not the same as the old and
  590. state.AllowEntryExpansion() is False
  591. """
  592. return True
  593. def WriteSymbols(self, section):
  594. """Write symbol values into binary files for access at run time
  595. Args:
  596. section: Section containing the entry
  597. """
  598. if self.auto_write_symbols and not self.no_write_symbols:
  599. # Check if we are writing symbols into an ELF file
  600. is_elf = self.GetDefaultFilename() == self.elf_fname
  601. elf.LookupAndWriteSymbols(self.elf_fname, self, section.GetImage(),
  602. is_elf, self.elf_base_sym)
  603. def CheckEntries(self):
  604. """Check that the entry offsets are correct
  605. This is used for entries which have extra offset requirements (other
  606. than having to be fully inside their section). Sub-classes can implement
  607. this function and raise if there is a problem.
  608. """
  609. pass
  610. @staticmethod
  611. def GetStr(value):
  612. if value is None:
  613. return '<none> '
  614. return '%08x' % value
  615. @staticmethod
  616. def WriteMapLine(fd, indent, name, offset, size, image_pos):
  617. print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
  618. Entry.GetStr(offset), Entry.GetStr(size),
  619. name), file=fd)
  620. def WriteMap(self, fd, indent):
  621. """Write a map of the entry to a .map file
  622. Args:
  623. fd: File to write the map to
  624. indent: Curent indent level of map (0=none, 1=one level, etc.)
  625. """
  626. self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
  627. self.image_pos)
  628. # pylint: disable=assignment-from-none
  629. def GetEntries(self):
  630. """Return a list of entries contained by this entry
  631. Returns:
  632. List of entries, or None if none. A normal entry has no entries
  633. within it so will return None
  634. """
  635. return None
  636. def FindEntryByNode(self, find_node):
  637. """Find a node in an entry, searching all subentries
  638. This does a recursive search.
  639. Args:
  640. find_node (fdt.Node): Node to find
  641. Returns:
  642. Entry: entry, if found, else None
  643. """
  644. entries = self.GetEntries()
  645. if entries:
  646. for entry in entries.values():
  647. if entry._node == find_node:
  648. return entry
  649. found = entry.FindEntryByNode(find_node)
  650. if found:
  651. return found
  652. return None
  653. def GetArg(self, name, datatype=str):
  654. """Get the value of an entry argument or device-tree-node property
  655. Some node properties can be provided as arguments to binman. First check
  656. the entry arguments, and fall back to the device tree if not found
  657. Args:
  658. name: Argument name
  659. datatype: Data type (str or int)
  660. Returns:
  661. Value of argument as a string or int, or None if no value
  662. Raises:
  663. ValueError if the argument cannot be converted to in
  664. """
  665. value = state.GetEntryArg(name)
  666. if value is not None:
  667. if datatype == int:
  668. try:
  669. value = int(value)
  670. except ValueError:
  671. self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
  672. (name, value))
  673. elif datatype == str:
  674. pass
  675. else:
  676. raise ValueError("GetArg() internal error: Unknown data type '%s'" %
  677. datatype)
  678. else:
  679. value = fdt_util.GetDatatype(self._node, name, datatype)
  680. return value
  681. @staticmethod
  682. def WriteDocs(modules, test_missing=None):
  683. """Write out documentation about the various entry types to stdout
  684. Args:
  685. modules: List of modules to include
  686. test_missing: Used for testing. This is a module to report
  687. as missing
  688. """
  689. print('''Binman Entry Documentation
  690. ===========================
  691. This file describes the entry types supported by binman. These entry types can
  692. be placed in an image one by one to build up a final firmware image. It is
  693. fairly easy to create new entry types. Just add a new file to the 'etype'
  694. directory. You can use the existing entries as examples.
  695. Note that some entries are subclasses of others, using and extending their
  696. features to produce new behaviours.
  697. ''')
  698. modules = sorted(modules)
  699. # Don't show the test entry
  700. if '_testing' in modules:
  701. modules.remove('_testing')
  702. missing = []
  703. for name in modules:
  704. module = Entry.Lookup('WriteDocs', name, False)
  705. docs = getattr(module, '__doc__')
  706. if test_missing == name:
  707. docs = None
  708. if docs:
  709. lines = docs.splitlines()
  710. first_line = lines[0]
  711. rest = [line[4:] for line in lines[1:]]
  712. hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
  713. # Create a reference for use by rST docs
  714. ref_name = f'etype_{module.__name__[6:]}'.lower()
  715. print('.. _%s:' % ref_name)
  716. print()
  717. print(hdr)
  718. print('-' * len(hdr))
  719. print('\n'.join(rest))
  720. print()
  721. print()
  722. else:
  723. missing.append(name)
  724. if missing:
  725. raise ValueError('Documentation is missing for modules: %s' %
  726. ', '.join(missing))
  727. def GetUniqueName(self):
  728. """Get a unique name for a node
  729. Returns:
  730. String containing a unique name for a node, consisting of the name
  731. of all ancestors (starting from within the 'binman' node) separated
  732. by a dot ('.'). This can be useful for generating unique filesnames
  733. in the output directory.
  734. """
  735. name = self.name
  736. node = self._node
  737. while node.parent:
  738. node = node.parent
  739. if node.name in ('binman', '/'):
  740. break
  741. name = '%s.%s' % (node.name, name)
  742. return name
  743. def extend_to_limit(self, limit):
  744. """Extend an entry so that it ends at the given offset limit"""
  745. if self.offset + self.size < limit:
  746. self.size = limit - self.offset
  747. # Request the contents again, since changing the size requires that
  748. # the data grows. This should not fail, but check it to be sure.
  749. if not self.ObtainContents():
  750. self.Raise('Cannot obtain contents when expanding entry')
  751. def HasSibling(self, name):
  752. """Check if there is a sibling of a given name
  753. Returns:
  754. True if there is an entry with this name in the the same section,
  755. else False
  756. """
  757. return name in self.section.GetEntries()
  758. def GetSiblingImagePos(self, name):
  759. """Return the image position of the given sibling
  760. Returns:
  761. Image position of sibling, or None if the sibling has no position,
  762. or False if there is no such sibling
  763. """
  764. if not self.HasSibling(name):
  765. return False
  766. return self.section.GetEntries()[name].image_pos
  767. @staticmethod
  768. def AddEntryInfo(entries, indent, name, etype, size, image_pos,
  769. uncomp_size, offset, entry):
  770. """Add a new entry to the entries list
  771. Args:
  772. entries: List (of EntryInfo objects) to add to
  773. indent: Current indent level to add to list
  774. name: Entry name (string)
  775. etype: Entry type (string)
  776. size: Entry size in bytes (int)
  777. image_pos: Position within image in bytes (int)
  778. uncomp_size: Uncompressed size if the entry uses compression, else
  779. None
  780. offset: Entry offset within parent in bytes (int)
  781. entry: Entry object
  782. """
  783. entries.append(EntryInfo(indent, name, etype, size, image_pos,
  784. uncomp_size, offset, entry))
  785. def ListEntries(self, entries, indent):
  786. """Add files in this entry to the list of entries
  787. This can be overridden by subclasses which need different behaviour.
  788. Args:
  789. entries: List (of EntryInfo objects) to add to
  790. indent: Current indent level to add to list
  791. """
  792. self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
  793. self.image_pos, self.uncomp_size, self.offset, self)
  794. def ReadData(self, decomp=True, alt_format=None):
  795. """Read the data for an entry from the image
  796. This is used when the image has been read in and we want to extract the
  797. data for a particular entry from that image.
  798. Args:
  799. decomp: True to decompress any compressed data before returning it;
  800. False to return the raw, uncompressed data
  801. Returns:
  802. Entry data (bytes)
  803. """
  804. # Use True here so that we get an uncompressed section to work from,
  805. # although compressed sections are currently not supported
  806. tout.debug("ReadChildData section '%s', entry '%s'" %
  807. (self.section.GetPath(), self.GetPath()))
  808. data = self.section.ReadChildData(self, decomp, alt_format)
  809. return data
  810. def ReadChildData(self, child, decomp=True, alt_format=None):
  811. """Read the data for a particular child entry
  812. This reads data from the parent and extracts the piece that relates to
  813. the given child.
  814. Args:
  815. child (Entry): Child entry to read data for (must be valid)
  816. decomp (bool): True to decompress any compressed data before
  817. returning it; False to return the raw, uncompressed data
  818. alt_format (str): Alternative format to read in, or None
  819. Returns:
  820. Data for the child (bytes)
  821. """
  822. pass
  823. def LoadData(self, decomp=True):
  824. data = self.ReadData(decomp)
  825. self.contents_size = len(data)
  826. self.ProcessContentsUpdate(data)
  827. self.Detail('Loaded data size %x' % len(data))
  828. def GetAltFormat(self, data, alt_format):
  829. """Read the data for an extry in an alternative format
  830. Supported formats are list in the documentation for each entry. An
  831. example is fdtmap which provides .
  832. Args:
  833. data (bytes): Data to convert (this should have been produced by the
  834. entry)
  835. alt_format (str): Format to use
  836. """
  837. pass
  838. def GetImage(self):
  839. """Get the image containing this entry
  840. Returns:
  841. Image object containing this entry
  842. """
  843. return self.section.GetImage()
  844. def WriteData(self, data, decomp=True):
  845. """Write the data to an entry in the image
  846. This is used when the image has been read in and we want to replace the
  847. data for a particular entry in that image.
  848. The image must be re-packed and written out afterwards.
  849. Args:
  850. data: Data to replace it with
  851. decomp: True to compress the data if needed, False if data is
  852. already compressed so should be used as is
  853. Returns:
  854. True if the data did not result in a resize of this entry, False if
  855. the entry must be resized
  856. """
  857. if self.size is not None:
  858. self.contents_size = self.size
  859. else:
  860. self.contents_size = self.pre_reset_size
  861. ok = self.ProcessContentsUpdate(data)
  862. self.build_done = False
  863. self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
  864. section_ok = self.section.WriteChildData(self)
  865. return ok and section_ok
  866. def WriteChildData(self, child):
  867. """Handle writing the data in a child entry
  868. This should be called on the child's parent section after the child's
  869. data has been updated. It should update any data structures needed to
  870. validate that the update is successful.
  871. This base-class implementation does nothing, since the base Entry object
  872. does not have any children.
  873. Args:
  874. child: Child Entry that was written
  875. Returns:
  876. True if the section could be updated successfully, False if the
  877. data is such that the section could not update
  878. """
  879. self.build_done = False
  880. entry = self.section
  881. # Now we must rebuild all sections above this one
  882. while entry and entry != entry.section:
  883. self.build_done = False
  884. entry = entry.section
  885. return True
  886. def GetSiblingOrder(self):
  887. """Get the relative order of an entry amoung its siblings
  888. Returns:
  889. 'start' if this entry is first among siblings, 'end' if last,
  890. otherwise None
  891. """
  892. entries = list(self.section.GetEntries().values())
  893. if entries:
  894. if self == entries[0]:
  895. return 'start'
  896. elif self == entries[-1]:
  897. return 'end'
  898. return 'middle'
  899. def SetAllowMissing(self, allow_missing):
  900. """Set whether a section allows missing external blobs
  901. Args:
  902. allow_missing: True if allowed, False if not allowed
  903. """
  904. # This is meaningless for anything other than sections
  905. pass
  906. def SetAllowFakeBlob(self, allow_fake):
  907. """Set whether a section allows to create a fake blob
  908. Args:
  909. allow_fake: True if allowed, False if not allowed
  910. """
  911. self.allow_fake = allow_fake
  912. def CheckMissing(self, missing_list):
  913. """Check if the entry has missing external blobs
  914. If there are missing (non-optional) blobs, the entries are added to the
  915. list
  916. Args:
  917. missing_list: List of Entry objects to be added to
  918. """
  919. if self.missing and not self.optional:
  920. missing_list.append(self)
  921. def check_fake_fname(self, fname, size=0):
  922. """If the file is missing and the entry allows fake blobs, fake it
  923. Sets self.faked to True if faked
  924. Args:
  925. fname (str): Filename to check
  926. size (int): Size of fake file to create
  927. Returns:
  928. tuple:
  929. fname (str): Filename of faked file
  930. bool: True if the blob was faked, False if not
  931. """
  932. if self.allow_fake and not pathlib.Path(fname).is_file():
  933. if not self.fake_fname:
  934. outfname = os.path.join(self.fake_dir, os.path.basename(fname))
  935. with open(outfname, "wb") as out:
  936. out.truncate(size)
  937. tout.info(f"Entry '{self._node.path}': Faked blob '{outfname}'")
  938. self.fake_fname = outfname
  939. self.faked = True
  940. return self.fake_fname, True
  941. return fname, False
  942. def CheckFakedBlobs(self, faked_blobs_list):
  943. """Check if any entries in this section have faked external blobs
  944. If there are faked blobs, the entries are added to the list
  945. Args:
  946. faked_blobs_list: List of Entry objects to be added to
  947. """
  948. # This is meaningless for anything other than blobs
  949. pass
  950. def CheckOptional(self, optional_list):
  951. """Check if the entry has missing but optional external blobs
  952. If there are missing (optional) blobs, the entries are added to the list
  953. Args:
  954. optional_list (list): List of Entry objects to be added to
  955. """
  956. if self.missing and self.optional:
  957. optional_list.append(self)
  958. def GetAllowMissing(self):
  959. """Get whether a section allows missing external blobs
  960. Returns:
  961. True if allowed, False if not allowed
  962. """
  963. return self.allow_missing
  964. def record_missing_bintool(self, bintool):
  965. """Record a missing bintool that was needed to produce this entry
  966. Args:
  967. bintool (Bintool): Bintool that was missing
  968. """
  969. if bintool not in self.missing_bintools:
  970. self.missing_bintools.append(bintool)
  971. def check_missing_bintools(self, missing_list):
  972. """Check if any entries in this section have missing bintools
  973. If there are missing bintools, these are added to the list
  974. Args:
  975. missing_list: List of Bintool objects to be added to
  976. """
  977. for bintool in self.missing_bintools:
  978. if bintool not in missing_list:
  979. missing_list.append(bintool)
  980. def GetHelpTags(self):
  981. """Get the tags use for missing-blob help
  982. Returns:
  983. list of possible tags, most desirable first
  984. """
  985. return list(filter(None, [self.missing_msg, self.name, self.etype]))
  986. def CompressData(self, indata):
  987. """Compress data according to the entry's compression method
  988. Args:
  989. indata: Data to compress
  990. Returns:
  991. Compressed data
  992. """
  993. self.uncomp_data = indata
  994. if self.compress != 'none':
  995. self.uncomp_size = len(indata)
  996. if self.comp_bintool.is_present():
  997. data = self.comp_bintool.compress(indata)
  998. else:
  999. self.record_missing_bintool(self.comp_bintool)
  1000. data = tools.get_bytes(0, 1024)
  1001. else:
  1002. data = indata
  1003. return data
  1004. def DecompressData(self, indata):
  1005. """Decompress data according to the entry's compression method
  1006. Args:
  1007. indata: Data to decompress
  1008. Returns:
  1009. Decompressed data
  1010. """
  1011. if self.compress != 'none':
  1012. if self.comp_bintool.is_present():
  1013. data = self.comp_bintool.decompress(indata)
  1014. self.uncomp_size = len(data)
  1015. else:
  1016. self.record_missing_bintool(self.comp_bintool)
  1017. data = tools.get_bytes(0, 1024)
  1018. else:
  1019. data = indata
  1020. self.uncomp_data = data
  1021. return data
  1022. @classmethod
  1023. def UseExpanded(cls, node, etype, new_etype):
  1024. """Check whether to use an expanded entry type
  1025. This is called by Entry.Create() when it finds an expanded version of
  1026. an entry type (e.g. 'u-boot-expanded'). If this method returns True then
  1027. it will be used (e.g. in place of 'u-boot'). If it returns False, it is
  1028. ignored.
  1029. Args:
  1030. node: Node object containing information about the entry to
  1031. create
  1032. etype: Original entry type being used
  1033. new_etype: New entry type proposed
  1034. Returns:
  1035. True to use this entry type, False to use the original one
  1036. """
  1037. tout.info("Node '%s': etype '%s': %s selected" %
  1038. (node.path, etype, new_etype))
  1039. return True
  1040. def CheckAltFormats(self, alt_formats):
  1041. """Add any alternative formats supported by this entry type
  1042. Args:
  1043. alt_formats (dict): Dict to add alt_formats to:
  1044. key: Name of alt format
  1045. value: Help text
  1046. """
  1047. pass
  1048. def AddBintools(self, btools):
  1049. """Add the bintools used by this entry type
  1050. Args:
  1051. btools (dict of Bintool):
  1052. Raise:
  1053. ValueError if compression algorithm is not supported
  1054. """
  1055. algo = self.compress
  1056. if algo != 'none':
  1057. algos = ['bzip2', 'gzip', 'lz4', 'lzma', 'lzo', 'xz', 'zstd']
  1058. if algo not in algos:
  1059. raise ValueError("Unknown algorithm '%s'" % algo)
  1060. names = {'lzma': 'lzma_alone', 'lzo': 'lzop'}
  1061. name = names.get(self.compress, self.compress)
  1062. self.comp_bintool = self.AddBintool(btools, name)
  1063. @classmethod
  1064. def AddBintool(self, tools, name):
  1065. """Add a new bintool to the tools used by this etype
  1066. Args:
  1067. name: Name of the tool
  1068. """
  1069. btool = bintool.Bintool.create(name)
  1070. tools[name] = btool
  1071. return btool
  1072. def SetUpdateHash(self, update_hash):
  1073. """Set whether this entry's "hash" subnode should be updated
  1074. Args:
  1075. update_hash: True if hash should be updated, False if not
  1076. """
  1077. self.update_hash = update_hash
  1078. def collect_contents_to_file(self, entries, prefix, fake_size=0):
  1079. """Put the contents of a list of entries into a file
  1080. Args:
  1081. entries (list of Entry): Entries to collect
  1082. prefix (str): Filename prefix of file to write to
  1083. fake_size (int): Size of fake file to create if needed
  1084. If any entry does not have contents yet, this function returns False
  1085. for the data.
  1086. Returns:
  1087. Tuple:
  1088. bytes: Concatenated data from all the entries (or None)
  1089. str: Filename of file written (or None if no data)
  1090. str: Unique portion of filename (or None if no data)
  1091. """
  1092. data = b''
  1093. for entry in entries:
  1094. data += entry.GetData()
  1095. uniq = self.GetUniqueName()
  1096. fname = tools.get_output_filename(f'{prefix}.{uniq}')
  1097. tools.write_file(fname, data)
  1098. return data, fname, uniq
  1099. @classmethod
  1100. def create_fake_dir(cls):
  1101. """Create the directory for fake files"""
  1102. cls.fake_dir = tools.get_output_filename('binman-fake')
  1103. if not os.path.exists(cls.fake_dir):
  1104. os.mkdir(cls.fake_dir)
  1105. tout.notice(f"Fake-blob dir is '{cls.fake_dir}'")
  1106. def ensure_props(self):
  1107. """Raise an exception if properties are missing
  1108. Args:
  1109. prop_list (list of str): List of properties to check for
  1110. Raises:
  1111. ValueError: Any property is missing
  1112. """
  1113. not_present = []
  1114. for prop in self.required_props:
  1115. if not prop in self._node.props:
  1116. not_present.append(prop)
  1117. if not_present:
  1118. self.Raise(f"'{self.etype}' entry is missing properties: {' '.join(not_present)}")
  1119. def mark_absent(self, msg):
  1120. tout.info("Entry '%s' marked absent: %s" % (self._node.path, msg))
  1121. self.absent = True
  1122. def read_elf_segments(self):
  1123. """Read segments from an entry that can generate an ELF file
  1124. Returns:
  1125. tuple:
  1126. list of segments, each:
  1127. int: Segment number (0 = first)
  1128. int: Start address of segment in memory
  1129. bytes: Contents of segment
  1130. int: entry address of ELF file
  1131. """
  1132. return None
  1133. def lookup_offset(self):
  1134. node, sym_name, offset = self.offset_from_elf
  1135. entry = self.section.FindEntryByNode(node)
  1136. if not entry:
  1137. self.Raise("Cannot find entry for node '%s'" % node.name)
  1138. if not entry.elf_fname:
  1139. entry.Raise("Need elf-fname property '%s'" % node.name)
  1140. val = elf.GetSymbolOffset(entry.elf_fname, sym_name,
  1141. entry.elf_base_sym)
  1142. return val + offset
  1143. def mark_build_done(self):
  1144. """Mark an entry as already built"""
  1145. self.build_done = True
  1146. entries = self.GetEntries()
  1147. if entries:
  1148. for entry in entries.values():
  1149. entry.mark_build_done()
  1150. def UpdateSignatures(self, privatekey_fname, algo, input_fname):
  1151. self.Raise('Updating signatures is not supported with this entry type')