| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384 |
- # SPDX-License-Identifier: GPL-2.0+
- # Copyright (c) 2016 Google, Inc
- #
- # Base class for all entries
- #
- from collections import namedtuple
- import importlib
- import os
- import pathlib
- import sys
- import time
- from binman import bintool
- from binman import elf
- from dtoc import fdt_util
- from u_boot_pylib import tools
- from u_boot_pylib.tools import to_hex, to_hex_size
- from u_boot_pylib import tout
- modules = {}
- # This is imported if needed
- state = None
- # An argument which can be passed to entries on the command line, in lieu of
- # device-tree properties.
- EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
- # Information about an entry for use when displaying summaries
- EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
- 'image_pos', 'uncomp_size', 'offset',
- 'entry'])
- class Entry(object):
- """An Entry in the section
- An entry corresponds to a single node in the device-tree description
- of the section. Each entry ends up being a part of the final section.
- Entries can be placed either right next to each other, or with padding
- between them. The type of the entry determines the data that is in it.
- This class is not used by itself. All entry objects are subclasses of
- Entry.
- Attributes:
- section: Section object containing this entry
- node: The node that created this entry
- offset: Offset of entry within the section, None if not known yet (in
- which case it will be calculated by Pack())
- size: Entry size in bytes, None if not known
- min_size: Minimum entry size in bytes
- pre_reset_size: size as it was before ResetForPack(). This allows us to
- keep track of the size we started with and detect size changes
- uncomp_size: Size of uncompressed data in bytes, if the entry is
- compressed, else None
- contents_size: Size of contents in bytes, 0 by default
- align: Entry start offset alignment relative to the start of the
- containing section, or None
- align_size: Entry size alignment, or None
- align_end: Entry end offset alignment relative to the start of the
- containing section, or None
- pad_before: Number of pad bytes before the contents when it is placed
- in the containing section, 0 if none. The pad bytes become part of
- the entry.
- pad_after: Number of pad bytes after the contents when it is placed in
- the containing section, 0 if none. The pad bytes become part of
- the entry.
- data: Contents of entry (string of bytes). This does not include
- padding created by pad_before or pad_after. If the entry is
- compressed, this contains the compressed data.
- uncomp_data: Original uncompressed data, if this entry is compressed,
- else None
- compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
- orig_offset: Original offset value read from node
- orig_size: Original size value read from node
- missing: True if this entry is missing its contents. Note that if it is
- optional, this entry will not appear in the list generated by
- entry.CheckMissing() since it is considered OK for it to be missing.
- allow_missing: Allow children of this entry to be missing (used by
- subclasses such as Entry_section)
- allow_fake: Allow creating a dummy fake file if the blob file is not
- available. This is mainly used for testing.
- external: True if this entry contains an external binary blob
- bintools: Bintools used by this entry (only populated for Image)
- missing_bintools: List of missing bintools for this entry
- update_hash: True if this entry's "hash" subnode should be
- updated with a hash of the entry contents
- comp_bintool: Bintools used for compress and decompress data
- fake_fname: Fake filename, if one was created, else None
- required_props (dict of str): Properties which must be present. This can
- be added to by subclasses
- elf_fname (str): Filename of the ELF file, if this entry holds an ELF
- file, or is a binary file produced from an ELF file
- auto_write_symbols (bool): True to write ELF symbols into this entry's
- contents
- absent (bool): True if this entry is absent. This can be controlled by
- the entry itself, allowing it to vanish in certain circumstances.
- An absent entry is removed during processing so that it does not
- appear in the map
- optional (bool): True if this entry contains an optional external blob
- overlap (bool): True if this entry overlaps with others
- preserve (bool): True if this entry should be preserved when updating
- firmware. This means that it will not be changed by the update.
- This is just a signal: enforcement of this is up to the updater.
- This flag does not automatically propagate down to child entries.
- build_done (bool): Indicates that the entry data has been built and does
- not need to be done again. This is only used with 'binman replace',
- to stop sections from being rebuilt if their entries have not been
- replaced
- """
- fake_dir = None
- def __init__(self, section, etype, node, name_prefix='',
- auto_write_symbols=False):
- # Put this here to allow entry-docs and help to work without libfdt
- global state
- from binman import state
- self.section = section
- self.etype = etype
- self._node = node
- self.name = node and (name_prefix + node.name) or 'none'
- self.offset = None
- self.size = None
- self.min_size = 0
- self.pre_reset_size = None
- self.uncomp_size = None
- self.data = None
- self.uncomp_data = None
- self.contents_size = 0
- self.align = None
- self.align_size = None
- self.align_end = None
- self.pad_before = 0
- self.pad_after = 0
- self.offset_unset = False
- self.image_pos = None
- self.extend_size = False
- self.compress = 'none'
- self.missing = False
- self.faked = False
- self.external = False
- self.allow_missing = False
- self.allow_fake = False
- self.bintools = {}
- self.missing_bintools = []
- self.update_hash = True
- self.fake_fname = None
- self.required_props = []
- self.comp_bintool = None
- self.elf_fname = None
- self.auto_write_symbols = auto_write_symbols
- self.absent = False
- self.optional = False
- self.overlap = False
- self.elf_base_sym = None
- self.offset_from_elf = None
- self.preserve = False
- self.build_done = False
- self.no_write_symbols = False
- @staticmethod
- def FindEntryClass(etype, expanded):
- """Look up the entry class for a node.
- Args:
- node_node: Path name of Node object containing information about
- the entry to create (used for errors)
- etype: Entry type to use
- expanded: Use the expanded version of etype
- Returns:
- The entry class object if found, else None if not found and expanded
- is True, else a tuple:
- module name that could not be found
- exception received
- """
- # Convert something like 'u-boot@0' to 'u_boot' since we are only
- # interested in the type.
- module_name = etype.replace('-', '_')
- if '@' in module_name:
- module_name = module_name.split('@')[0]
- if expanded:
- module_name += '_expanded'
- module = modules.get(module_name)
- # Also allow entry-type modules to be brought in from the etype directory.
- # Import the module if we have not already done so.
- if not module:
- try:
- module = importlib.import_module('binman.etype.' + module_name)
- except ImportError as e:
- if expanded:
- return None
- return module_name, e
- modules[module_name] = module
- # Look up the expected class name
- return getattr(module, 'Entry_%s' % module_name)
- @staticmethod
- def Lookup(node_path, etype, expanded, missing_etype=False):
- """Look up the entry class for a node.
- Args:
- node_node (str): Path name of Node object containing information
- about the entry to create (used for errors)
- etype (str): Entry type to use
- expanded (bool): Use the expanded version of etype
- missing_etype (bool): True to default to a blob etype if the
- requested etype is not found
- Returns:
- The entry class object if found, else None if not found and expanded
- is True
- Raise:
- ValueError if expanded is False and the class is not found
- """
- # Convert something like 'u-boot@0' to 'u_boot' since we are only
- # interested in the type.
- cls = Entry.FindEntryClass(etype, expanded)
- if cls is None:
- return None
- elif isinstance(cls, tuple):
- if missing_etype:
- cls = Entry.FindEntryClass('blob', False)
- if isinstance(cls, tuple): # This should not fail
- module_name, e = cls
- raise ValueError(
- "Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
- (etype, node_path, module_name, e))
- return cls
- @staticmethod
- def Create(section, node, etype=None, expanded=False, missing_etype=False):
- """Create a new entry for a node.
- Args:
- section (entry_Section): Section object containing this node
- node (Node): Node object containing information about the entry to
- create
- etype (str): Entry type to use, or None to work it out (used for
- tests)
- expanded (bool): Use the expanded version of etype
- missing_etype (bool): True to default to a blob etype if the
- requested etype is not found
- Returns:
- A new Entry object of the correct type (a subclass of Entry)
- """
- if not etype:
- etype = fdt_util.GetString(node, 'type', node.name)
- obj = Entry.Lookup(node.path, etype, expanded, missing_etype)
- if obj and expanded:
- # Check whether to use the expanded entry
- new_etype = etype + '-expanded'
- can_expand = not fdt_util.GetBool(node, 'no-expanded')
- if can_expand and obj.UseExpanded(node, etype, new_etype):
- etype = new_etype
- else:
- obj = None
- if not obj:
- obj = Entry.Lookup(node.path, etype, False, missing_etype)
- # Call its constructor to get the object we want.
- return obj(section, etype, node)
- def ReadNode(self):
- """Read entry information from the node
- This must be called as the first thing after the Entry is created.
- This reads all the fields we recognise from the node, ready for use.
- """
- self.ensure_props()
- if 'pos' in self._node.props:
- self.Raise("Please use 'offset' instead of 'pos'")
- if 'expand-size' in self._node.props:
- self.Raise("Please use 'extend-size' instead of 'expand-size'")
- self.offset = fdt_util.GetInt(self._node, 'offset')
- self.size = fdt_util.GetInt(self._node, 'size')
- self.min_size = fdt_util.GetInt(self._node, 'min-size', 0)
- self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
- self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
- if self.GetImage().copy_to_orig:
- self.orig_offset = self.offset
- self.orig_size = self.size
- # These should not be set in input files, but are set in an FDT map,
- # which is also read by this code.
- self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
- self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
- self.align = fdt_util.GetInt(self._node, 'align')
- if tools.not_power_of_two(self.align):
- raise ValueError("Node '%s': Alignment %s must be a power of two" %
- (self._node.path, self.align))
- if self.section and self.align is None:
- self.align = self.section.align_default
- self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
- self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
- self.align_size = fdt_util.GetInt(self._node, 'align-size')
- if tools.not_power_of_two(self.align_size):
- self.Raise("Alignment size %s must be a power of two" %
- self.align_size)
- self.align_end = fdt_util.GetInt(self._node, 'align-end')
- self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
- self.extend_size = fdt_util.GetBool(self._node, 'extend-size')
- self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
- self.optional = fdt_util.GetBool(self._node, 'optional')
- self.overlap = fdt_util.GetBool(self._node, 'overlap')
- if self.overlap:
- self.required_props += ['offset', 'size']
- # This is only supported by blobs and sections at present
- self.compress = fdt_util.GetString(self._node, 'compress', 'none')
- self.offset_from_elf = fdt_util.GetPhandleNameOffset(self._node,
- 'offset-from-elf')
- self.preserve = fdt_util.GetBool(self._node, 'preserve')
- self.no_write_symbols = fdt_util.GetBool(self._node, 'no-write-symbols')
- def GetDefaultFilename(self):
- return None
- def GetFdts(self):
- """Get the device trees used by this entry
- Returns:
- Empty dict, if this entry is not a .dtb, otherwise:
- Dict:
- key: Filename from this entry (without the path)
- value: Tuple:
- Entry object for this dtb
- Filename of file containing this dtb
- """
- return {}
- def gen_entries(self):
- """Allow entries to generate other entries
- Some entries generate subnodes automatically, from which sub-entries
- are then created. This method allows those to be added to the binman
- definition for the current image. An entry which implements this method
- should call state.AddSubnode() to add a subnode and can add properties
- with state.AddString(), etc.
- An example is 'files', which produces a section containing a list of
- files.
- """
- pass
- def AddMissingProperties(self, have_image_pos):
- """Add new properties to the device tree as needed for this entry
- Args:
- have_image_pos: True if this entry has an image position. This can
- be False if its parent section is compressed, since compression
- groups all entries together into a compressed block of data,
- obscuring the start of each individual child entry
- """
- for prop in ['offset', 'size']:
- if not prop in self._node.props:
- state.AddZeroProp(self._node, prop)
- if have_image_pos and 'image-pos' not in self._node.props:
- state.AddZeroProp(self._node, 'image-pos')
- if self.GetImage().allow_repack:
- if self.orig_offset is not None:
- state.AddZeroProp(self._node, 'orig-offset', True)
- if self.orig_size is not None:
- state.AddZeroProp(self._node, 'orig-size', True)
- if self.compress != 'none':
- state.AddZeroProp(self._node, 'uncomp-size')
- if self.update_hash:
- err = state.CheckAddHashProp(self._node)
- if err:
- self.Raise(err)
- def SetCalculatedProperties(self):
- """Set the value of device-tree properties calculated by binman"""
- state.SetInt(self._node, 'offset', self.offset)
- state.SetInt(self._node, 'size', self.size)
- base = self.section.GetRootSkipAtStart() if self.section else 0
- if self.image_pos is not None:
- state.SetInt(self._node, 'image-pos', self.image_pos - base)
- if self.GetImage().allow_repack:
- if self.orig_offset is not None:
- state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
- if self.orig_size is not None:
- state.SetInt(self._node, 'orig-size', self.orig_size, True)
- if self.uncomp_size is not None:
- state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
- if self.update_hash:
- state.CheckSetHashValue(self._node, self.GetData)
- def ProcessFdt(self, fdt):
- """Allow entries to adjust the device tree
- Some entries need to adjust the device tree for their purposes. This
- may involve adding or deleting properties.
- Returns:
- True if processing is complete
- False if processing could not be completed due to a dependency.
- This will cause the entry to be retried after others have been
- called
- """
- return True
- def SetPrefix(self, prefix):
- """Set the name prefix for a node
- Args:
- prefix: Prefix to set, or '' to not use a prefix
- """
- if prefix:
- self.name = prefix + self.name
- def SetContents(self, data):
- """Set the contents of an entry
- This sets both the data and content_size properties
- Args:
- data: Data to set to the contents (bytes)
- """
- self.data = data
- self.contents_size = len(self.data)
- def ProcessContentsUpdate(self, data):
- """Update the contents of an entry, after the size is fixed
- This checks that the new data is the same size as the old. If the size
- has changed, this triggers a re-run of the packing algorithm.
- Args:
- data: Data to set to the contents (bytes)
- Raises:
- ValueError if the new data size is not the same as the old
- """
- size_ok = True
- new_size = len(data)
- if state.AllowEntryExpansion() and new_size > self.contents_size:
- # self.data will indicate the new size needed
- size_ok = False
- elif state.AllowEntryContraction() and new_size < self.contents_size:
- size_ok = False
- # If not allowed to change, try to deal with it or give up
- if size_ok:
- if new_size > self.contents_size:
- self.Raise('Cannot update entry size from %d to %d' %
- (self.contents_size, new_size))
- # Don't let the data shrink. Pad it if necessary
- if size_ok and new_size < self.contents_size:
- data += tools.get_bytes(0, self.contents_size - new_size)
- if not size_ok:
- tout.debug("Entry '%s' size change from %s to %s" % (
- self._node.path, to_hex(self.contents_size),
- to_hex(new_size)))
- self.SetContents(data)
- return size_ok
- def ObtainContents(self, skip_entry=None, fake_size=0):
- """Figure out the contents of an entry.
- For missing blobs (where allow-missing is enabled), the contents are set
- to b'' and self.missing is set to True.
- Args:
- skip_entry (Entry): Entry to skip when obtaining section contents
- fake_size (int): Size of fake file to create if needed
- Returns:
- True if the contents were found, False if another call is needed
- after the other entries are processed, None if there is no contents
- """
- # No contents by default: subclasses can implement this
- return True
- def ResetForPack(self):
- """Reset offset/size fields so that packing can be done again"""
- self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
- (to_hex(self.offset), to_hex(self.orig_offset),
- to_hex(self.size), to_hex(self.orig_size)))
- self.pre_reset_size = self.size
- self.offset = self.orig_offset
- self.size = self.orig_size
- def Pack(self, offset):
- """Figure out how to pack the entry into the section
- Most of the time the entries are not fully specified. There may be
- an alignment but no size. In that case we take the size from the
- contents of the entry.
- If an entry has no hard-coded offset, it will be placed at @offset.
- Once this function is complete, both the offset and size of the
- entry will be know.
- Args:
- Current section offset pointer
- Returns:
- New section offset pointer (after this entry)
- """
- self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
- (to_hex(self.offset), to_hex(self.size),
- self.contents_size))
- if self.offset is None:
- if self.offset_unset:
- self.Raise('No offset set with offset-unset: should another '
- 'entry provide this correct offset?')
- elif self.offset_from_elf:
- self.offset = self.lookup_offset()
- else:
- self.offset = tools.align(offset, self.align)
- needed = self.pad_before + self.contents_size + self.pad_after
- needed = max(needed, self.min_size)
- needed = tools.align(needed, self.align_size)
- size = self.size
- if not size:
- size = needed
- new_offset = self.offset + size
- aligned_offset = tools.align(new_offset, self.align_end)
- if aligned_offset != new_offset:
- size = aligned_offset - self.offset
- new_offset = aligned_offset
- if not self.size:
- self.size = size
- if self.size < needed:
- self.Raise("Entry contents size is %#x (%d) but entry size is "
- "%#x (%d)" % (needed, needed, self.size, self.size))
- # Check that the alignment is correct. It could be wrong if the
- # and offset or size values were provided (i.e. not calculated), but
- # conflict with the provided alignment values
- if self.size != tools.align(self.size, self.align_size):
- self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
- (self.size, self.size, self.align_size, self.align_size))
- if self.offset != tools.align(self.offset, self.align):
- self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
- (self.offset, self.offset, self.align, self.align))
- self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
- (self.offset, self.size, self.contents_size, new_offset))
- return new_offset
- def Raise(self, msg):
- """Convenience function to raise an error referencing a node"""
- raise ValueError("Node '%s': %s" % (self._node.path, msg))
- def Info(self, msg):
- """Convenience function to log info referencing a node"""
- tag = "Info '%s'" % self._node.path
- tout.detail('%30s: %s' % (tag, msg))
- def Detail(self, msg):
- """Convenience function to log detail referencing a node"""
- tag = "Node '%s'" % self._node.path
- tout.detail('%30s: %s' % (tag, msg))
- def GetEntryArgsOrProps(self, props, required=False):
- """Return the values of a set of properties
- Args:
- props: List of EntryArg objects
- Raises:
- ValueError if a property is not found
- """
- values = []
- missing = []
- for prop in props:
- python_prop = prop.name.replace('-', '_')
- if hasattr(self, python_prop):
- value = getattr(self, python_prop)
- else:
- value = None
- if value is None:
- value = self.GetArg(prop.name, prop.datatype)
- if value is None and required:
- missing.append(prop.name)
- values.append(value)
- if missing:
- self.GetImage().MissingArgs(self, missing)
- return values
- def GetPath(self):
- """Get the path of a node
- Returns:
- Full path of the node for this entry
- """
- return self._node.path
- def GetData(self, required=True):
- """Get the contents of an entry
- Args:
- required: True if the data must be present, False if it is OK to
- return None
- Returns:
- bytes content of the entry, excluding any padding. If the entry is
- compressed, the compressed data is returned. If the entry data
- is not yet available, False can be returned. If the entry data
- is null, then None is returned.
- """
- self.Detail('GetData: size %s' % to_hex_size(self.data))
- return self.data
- def GetPaddedData(self, data=None):
- """Get the data for an entry including any padding
- Gets the entry data and uses its section's pad-byte value to add padding
- before and after as defined by the pad-before and pad-after properties.
- This does not consider alignment.
- Returns:
- Contents of the entry along with any pad bytes before and
- after it (bytes)
- """
- if data is None:
- data = self.GetData()
- return self.section.GetPaddedDataForEntry(self, data)
- def GetOffsets(self):
- """Get the offsets for siblings
- Some entry types can contain information about the position or size of
- other entries. An example of this is the Intel Flash Descriptor, which
- knows where the Intel Management Engine section should go.
- If this entry knows about the position of other entries, it can specify
- this by returning values here
- Returns:
- Dict:
- key: Entry type
- value: List containing position and size of the given entry
- type. Either can be None if not known
- """
- return {}
- def SetOffsetSize(self, offset, size):
- """Set the offset and/or size of an entry
- Args:
- offset: New offset, or None to leave alone
- size: New size, or None to leave alone
- """
- if offset is not None:
- self.offset = offset
- if size is not None:
- self.size = size
- def SetImagePos(self, image_pos):
- """Set the position in the image
- Args:
- image_pos: Position of this entry in the image
- """
- self.image_pos = image_pos + self.offset
- def ProcessContents(self):
- """Do any post-packing updates of entry contents
- This function should call ProcessContentsUpdate() to update the entry
- contents, if necessary, returning its return value here.
- Args:
- data: Data to set to the contents (bytes)
- Returns:
- True if the new data size is OK, False if expansion is needed
- Raises:
- ValueError if the new data size is not the same as the old and
- state.AllowEntryExpansion() is False
- """
- return True
- def WriteSymbols(self, section):
- """Write symbol values into binary files for access at run time
- Args:
- section: Section containing the entry
- """
- if self.auto_write_symbols and not self.no_write_symbols:
- # Check if we are writing symbols into an ELF file
- is_elf = self.GetDefaultFilename() == self.elf_fname
- elf.LookupAndWriteSymbols(self.elf_fname, self, section.GetImage(),
- is_elf, self.elf_base_sym)
- def CheckEntries(self):
- """Check that the entry offsets are correct
- This is used for entries which have extra offset requirements (other
- than having to be fully inside their section). Sub-classes can implement
- this function and raise if there is a problem.
- """
- pass
- @staticmethod
- def GetStr(value):
- if value is None:
- return '<none> '
- return '%08x' % value
- @staticmethod
- def WriteMapLine(fd, indent, name, offset, size, image_pos):
- print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
- Entry.GetStr(offset), Entry.GetStr(size),
- name), file=fd)
- def WriteMap(self, fd, indent):
- """Write a map of the entry to a .map file
- Args:
- fd: File to write the map to
- indent: Curent indent level of map (0=none, 1=one level, etc.)
- """
- self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
- self.image_pos)
- # pylint: disable=assignment-from-none
- def GetEntries(self):
- """Return a list of entries contained by this entry
- Returns:
- List of entries, or None if none. A normal entry has no entries
- within it so will return None
- """
- return None
- def FindEntryByNode(self, find_node):
- """Find a node in an entry, searching all subentries
- This does a recursive search.
- Args:
- find_node (fdt.Node): Node to find
- Returns:
- Entry: entry, if found, else None
- """
- entries = self.GetEntries()
- if entries:
- for entry in entries.values():
- if entry._node == find_node:
- return entry
- found = entry.FindEntryByNode(find_node)
- if found:
- return found
- return None
- def GetArg(self, name, datatype=str):
- """Get the value of an entry argument or device-tree-node property
- Some node properties can be provided as arguments to binman. First check
- the entry arguments, and fall back to the device tree if not found
- Args:
- name: Argument name
- datatype: Data type (str or int)
- Returns:
- Value of argument as a string or int, or None if no value
- Raises:
- ValueError if the argument cannot be converted to in
- """
- value = state.GetEntryArg(name)
- if value is not None:
- if datatype == int:
- try:
- value = int(value)
- except ValueError:
- self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
- (name, value))
- elif datatype == str:
- pass
- else:
- raise ValueError("GetArg() internal error: Unknown data type '%s'" %
- datatype)
- else:
- value = fdt_util.GetDatatype(self._node, name, datatype)
- return value
- @staticmethod
- def WriteDocs(modules, test_missing=None):
- """Write out documentation about the various entry types to stdout
- Args:
- modules: List of modules to include
- test_missing: Used for testing. This is a module to report
- as missing
- """
- print('''Binman Entry Documentation
- ===========================
- This file describes the entry types supported by binman. These entry types can
- be placed in an image one by one to build up a final firmware image. It is
- fairly easy to create new entry types. Just add a new file to the 'etype'
- directory. You can use the existing entries as examples.
- Note that some entries are subclasses of others, using and extending their
- features to produce new behaviours.
- ''')
- modules = sorted(modules)
- # Don't show the test entry
- if '_testing' in modules:
- modules.remove('_testing')
- missing = []
- for name in modules:
- module = Entry.Lookup('WriteDocs', name, False)
- docs = getattr(module, '__doc__')
- if test_missing == name:
- docs = None
- if docs:
- lines = docs.splitlines()
- first_line = lines[0]
- rest = [line[4:] for line in lines[1:]]
- hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
- # Create a reference for use by rST docs
- ref_name = f'etype_{module.__name__[6:]}'.lower()
- print('.. _%s:' % ref_name)
- print()
- print(hdr)
- print('-' * len(hdr))
- print('\n'.join(rest))
- print()
- print()
- else:
- missing.append(name)
- if missing:
- raise ValueError('Documentation is missing for modules: %s' %
- ', '.join(missing))
- def GetUniqueName(self):
- """Get a unique name for a node
- Returns:
- String containing a unique name for a node, consisting of the name
- of all ancestors (starting from within the 'binman' node) separated
- by a dot ('.'). This can be useful for generating unique filesnames
- in the output directory.
- """
- name = self.name
- node = self._node
- while node.parent:
- node = node.parent
- if node.name in ('binman', '/'):
- break
- name = '%s.%s' % (node.name, name)
- return name
- def extend_to_limit(self, limit):
- """Extend an entry so that it ends at the given offset limit"""
- if self.offset + self.size < limit:
- self.size = limit - self.offset
- # Request the contents again, since changing the size requires that
- # the data grows. This should not fail, but check it to be sure.
- if not self.ObtainContents():
- self.Raise('Cannot obtain contents when expanding entry')
- def HasSibling(self, name):
- """Check if there is a sibling of a given name
- Returns:
- True if there is an entry with this name in the the same section,
- else False
- """
- return name in self.section.GetEntries()
- def GetSiblingImagePos(self, name):
- """Return the image position of the given sibling
- Returns:
- Image position of sibling, or None if the sibling has no position,
- or False if there is no such sibling
- """
- if not self.HasSibling(name):
- return False
- return self.section.GetEntries()[name].image_pos
- @staticmethod
- def AddEntryInfo(entries, indent, name, etype, size, image_pos,
- uncomp_size, offset, entry):
- """Add a new entry to the entries list
- Args:
- entries: List (of EntryInfo objects) to add to
- indent: Current indent level to add to list
- name: Entry name (string)
- etype: Entry type (string)
- size: Entry size in bytes (int)
- image_pos: Position within image in bytes (int)
- uncomp_size: Uncompressed size if the entry uses compression, else
- None
- offset: Entry offset within parent in bytes (int)
- entry: Entry object
- """
- entries.append(EntryInfo(indent, name, etype, size, image_pos,
- uncomp_size, offset, entry))
- def ListEntries(self, entries, indent):
- """Add files in this entry to the list of entries
- This can be overridden by subclasses which need different behaviour.
- Args:
- entries: List (of EntryInfo objects) to add to
- indent: Current indent level to add to list
- """
- self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
- self.image_pos, self.uncomp_size, self.offset, self)
- def ReadData(self, decomp=True, alt_format=None):
- """Read the data for an entry from the image
- This is used when the image has been read in and we want to extract the
- data for a particular entry from that image.
- Args:
- decomp: True to decompress any compressed data before returning it;
- False to return the raw, uncompressed data
- Returns:
- Entry data (bytes)
- """
- # Use True here so that we get an uncompressed section to work from,
- # although compressed sections are currently not supported
- tout.debug("ReadChildData section '%s', entry '%s'" %
- (self.section.GetPath(), self.GetPath()))
- data = self.section.ReadChildData(self, decomp, alt_format)
- return data
- def ReadChildData(self, child, decomp=True, alt_format=None):
- """Read the data for a particular child entry
- This reads data from the parent and extracts the piece that relates to
- the given child.
- Args:
- child (Entry): Child entry to read data for (must be valid)
- decomp (bool): True to decompress any compressed data before
- returning it; False to return the raw, uncompressed data
- alt_format (str): Alternative format to read in, or None
- Returns:
- Data for the child (bytes)
- """
- pass
- def LoadData(self, decomp=True):
- data = self.ReadData(decomp)
- self.contents_size = len(data)
- self.ProcessContentsUpdate(data)
- self.Detail('Loaded data size %x' % len(data))
- def GetAltFormat(self, data, alt_format):
- """Read the data for an extry in an alternative format
- Supported formats are list in the documentation for each entry. An
- example is fdtmap which provides .
- Args:
- data (bytes): Data to convert (this should have been produced by the
- entry)
- alt_format (str): Format to use
- """
- pass
- def GetImage(self):
- """Get the image containing this entry
- Returns:
- Image object containing this entry
- """
- return self.section.GetImage()
- def WriteData(self, data, decomp=True):
- """Write the data to an entry in the image
- This is used when the image has been read in and we want to replace the
- data for a particular entry in that image.
- The image must be re-packed and written out afterwards.
- Args:
- data: Data to replace it with
- decomp: True to compress the data if needed, False if data is
- already compressed so should be used as is
- Returns:
- True if the data did not result in a resize of this entry, False if
- the entry must be resized
- """
- if self.size is not None:
- self.contents_size = self.size
- else:
- self.contents_size = self.pre_reset_size
- ok = self.ProcessContentsUpdate(data)
- self.build_done = False
- self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
- section_ok = self.section.WriteChildData(self)
- return ok and section_ok
- def WriteChildData(self, child):
- """Handle writing the data in a child entry
- This should be called on the child's parent section after the child's
- data has been updated. It should update any data structures needed to
- validate that the update is successful.
- This base-class implementation does nothing, since the base Entry object
- does not have any children.
- Args:
- child: Child Entry that was written
- Returns:
- True if the section could be updated successfully, False if the
- data is such that the section could not update
- """
- self.build_done = False
- entry = self.section
- # Now we must rebuild all sections above this one
- while entry and entry != entry.section:
- self.build_done = False
- entry = entry.section
- return True
- def GetSiblingOrder(self):
- """Get the relative order of an entry amoung its siblings
- Returns:
- 'start' if this entry is first among siblings, 'end' if last,
- otherwise None
- """
- entries = list(self.section.GetEntries().values())
- if entries:
- if self == entries[0]:
- return 'start'
- elif self == entries[-1]:
- return 'end'
- return 'middle'
- def SetAllowMissing(self, allow_missing):
- """Set whether a section allows missing external blobs
- Args:
- allow_missing: True if allowed, False if not allowed
- """
- # This is meaningless for anything other than sections
- pass
- def SetAllowFakeBlob(self, allow_fake):
- """Set whether a section allows to create a fake blob
- Args:
- allow_fake: True if allowed, False if not allowed
- """
- self.allow_fake = allow_fake
- def CheckMissing(self, missing_list):
- """Check if the entry has missing external blobs
- If there are missing (non-optional) blobs, the entries are added to the
- list
- Args:
- missing_list: List of Entry objects to be added to
- """
- if self.missing and not self.optional:
- missing_list.append(self)
- def check_fake_fname(self, fname, size=0):
- """If the file is missing and the entry allows fake blobs, fake it
- Sets self.faked to True if faked
- Args:
- fname (str): Filename to check
- size (int): Size of fake file to create
- Returns:
- tuple:
- fname (str): Filename of faked file
- bool: True if the blob was faked, False if not
- """
- if self.allow_fake and not pathlib.Path(fname).is_file():
- if not self.fake_fname:
- outfname = os.path.join(self.fake_dir, os.path.basename(fname))
- with open(outfname, "wb") as out:
- out.truncate(size)
- tout.info(f"Entry '{self._node.path}': Faked blob '{outfname}'")
- self.fake_fname = outfname
- self.faked = True
- return self.fake_fname, True
- return fname, False
- def CheckFakedBlobs(self, faked_blobs_list):
- """Check if any entries in this section have faked external blobs
- If there are faked blobs, the entries are added to the list
- Args:
- faked_blobs_list: List of Entry objects to be added to
- """
- # This is meaningless for anything other than blobs
- pass
- def CheckOptional(self, optional_list):
- """Check if the entry has missing but optional external blobs
- If there are missing (optional) blobs, the entries are added to the list
- Args:
- optional_list (list): List of Entry objects to be added to
- """
- if self.missing and self.optional:
- optional_list.append(self)
- def GetAllowMissing(self):
- """Get whether a section allows missing external blobs
- Returns:
- True if allowed, False if not allowed
- """
- return self.allow_missing
- def record_missing_bintool(self, bintool):
- """Record a missing bintool that was needed to produce this entry
- Args:
- bintool (Bintool): Bintool that was missing
- """
- if bintool not in self.missing_bintools:
- self.missing_bintools.append(bintool)
- def check_missing_bintools(self, missing_list):
- """Check if any entries in this section have missing bintools
- If there are missing bintools, these are added to the list
- Args:
- missing_list: List of Bintool objects to be added to
- """
- for bintool in self.missing_bintools:
- if bintool not in missing_list:
- missing_list.append(bintool)
- def GetHelpTags(self):
- """Get the tags use for missing-blob help
- Returns:
- list of possible tags, most desirable first
- """
- return list(filter(None, [self.missing_msg, self.name, self.etype]))
- def CompressData(self, indata):
- """Compress data according to the entry's compression method
- Args:
- indata: Data to compress
- Returns:
- Compressed data
- """
- self.uncomp_data = indata
- if self.compress != 'none':
- self.uncomp_size = len(indata)
- if self.comp_bintool.is_present():
- data = self.comp_bintool.compress(indata)
- else:
- self.record_missing_bintool(self.comp_bintool)
- data = tools.get_bytes(0, 1024)
- else:
- data = indata
- return data
- def DecompressData(self, indata):
- """Decompress data according to the entry's compression method
- Args:
- indata: Data to decompress
- Returns:
- Decompressed data
- """
- if self.compress != 'none':
- if self.comp_bintool.is_present():
- data = self.comp_bintool.decompress(indata)
- self.uncomp_size = len(data)
- else:
- self.record_missing_bintool(self.comp_bintool)
- data = tools.get_bytes(0, 1024)
- else:
- data = indata
- self.uncomp_data = data
- return data
- @classmethod
- def UseExpanded(cls, node, etype, new_etype):
- """Check whether to use an expanded entry type
- This is called by Entry.Create() when it finds an expanded version of
- an entry type (e.g. 'u-boot-expanded'). If this method returns True then
- it will be used (e.g. in place of 'u-boot'). If it returns False, it is
- ignored.
- Args:
- node: Node object containing information about the entry to
- create
- etype: Original entry type being used
- new_etype: New entry type proposed
- Returns:
- True to use this entry type, False to use the original one
- """
- tout.info("Node '%s': etype '%s': %s selected" %
- (node.path, etype, new_etype))
- return True
- def CheckAltFormats(self, alt_formats):
- """Add any alternative formats supported by this entry type
- Args:
- alt_formats (dict): Dict to add alt_formats to:
- key: Name of alt format
- value: Help text
- """
- pass
- def AddBintools(self, btools):
- """Add the bintools used by this entry type
- Args:
- btools (dict of Bintool):
- Raise:
- ValueError if compression algorithm is not supported
- """
- algo = self.compress
- if algo != 'none':
- algos = ['bzip2', 'gzip', 'lz4', 'lzma', 'lzo', 'xz', 'zstd']
- if algo not in algos:
- raise ValueError("Unknown algorithm '%s'" % algo)
- names = {'lzma': 'lzma_alone', 'lzo': 'lzop'}
- name = names.get(self.compress, self.compress)
- self.comp_bintool = self.AddBintool(btools, name)
- @classmethod
- def AddBintool(self, tools, name):
- """Add a new bintool to the tools used by this etype
- Args:
- name: Name of the tool
- """
- btool = bintool.Bintool.create(name)
- tools[name] = btool
- return btool
- def SetUpdateHash(self, update_hash):
- """Set whether this entry's "hash" subnode should be updated
- Args:
- update_hash: True if hash should be updated, False if not
- """
- self.update_hash = update_hash
- def collect_contents_to_file(self, entries, prefix, fake_size=0):
- """Put the contents of a list of entries into a file
- Args:
- entries (list of Entry): Entries to collect
- prefix (str): Filename prefix of file to write to
- fake_size (int): Size of fake file to create if needed
- If any entry does not have contents yet, this function returns False
- for the data.
- Returns:
- Tuple:
- bytes: Concatenated data from all the entries (or None)
- str: Filename of file written (or None if no data)
- str: Unique portion of filename (or None if no data)
- """
- data = b''
- for entry in entries:
- data += entry.GetData()
- uniq = self.GetUniqueName()
- fname = tools.get_output_filename(f'{prefix}.{uniq}')
- tools.write_file(fname, data)
- return data, fname, uniq
- @classmethod
- def create_fake_dir(cls):
- """Create the directory for fake files"""
- cls.fake_dir = tools.get_output_filename('binman-fake')
- if not os.path.exists(cls.fake_dir):
- os.mkdir(cls.fake_dir)
- tout.notice(f"Fake-blob dir is '{cls.fake_dir}'")
- def ensure_props(self):
- """Raise an exception if properties are missing
- Args:
- prop_list (list of str): List of properties to check for
- Raises:
- ValueError: Any property is missing
- """
- not_present = []
- for prop in self.required_props:
- if not prop in self._node.props:
- not_present.append(prop)
- if not_present:
- self.Raise(f"'{self.etype}' entry is missing properties: {' '.join(not_present)}")
- def mark_absent(self, msg):
- tout.info("Entry '%s' marked absent: %s" % (self._node.path, msg))
- self.absent = True
- def read_elf_segments(self):
- """Read segments from an entry that can generate an ELF file
- Returns:
- tuple:
- list of segments, each:
- int: Segment number (0 = first)
- int: Start address of segment in memory
- bytes: Contents of segment
- int: entry address of ELF file
- """
- return None
- def lookup_offset(self):
- node, sym_name, offset = self.offset_from_elf
- entry = self.section.FindEntryByNode(node)
- if not entry:
- self.Raise("Cannot find entry for node '%s'" % node.name)
- if not entry.elf_fname:
- entry.Raise("Need elf-fname property '%s'" % node.name)
- val = elf.GetSymbolOffset(entry.elf_fname, sym_name,
- entry.elf_base_sym)
- return val + offset
- def mark_build_done(self):
- """Mark an entry as already built"""
- self.build_done = True
- entries = self.GetEntries()
- if entries:
- for entry in entries.values():
- entry.mark_build_done()
- def UpdateSignatures(self, privatekey_fname, algo, input_fname):
- self.Raise('Updating signatures is not supported with this entry type')
|