aboutsummaryrefslogtreecommitdiff
path: root/tools/binman/etype
diff options
context:
space:
mode:
Diffstat (limited to 'tools/binman/etype')
-rw-r--r--tools/binman/etype/atf_fip.py273
-rw-r--r--tools/binman/etype/blob.py16
-rw-r--r--tools/binman/etype/blob_ext_list.py58
-rw-r--r--tools/binman/etype/blob_phase.py2
-rw-r--r--tools/binman/etype/cbfs.py98
-rw-r--r--tools/binman/etype/fdtmap.py12
-rw-r--r--tools/binman/etype/files.py2
-rw-r--r--tools/binman/etype/fit.py4
-rw-r--r--tools/binman/etype/intel_ifwi.py4
-rw-r--r--tools/binman/etype/mkimage.py4
-rw-r--r--tools/binman/etype/section.py214
11 files changed, 583 insertions, 104 deletions
diff --git a/tools/binman/etype/atf_fip.py b/tools/binman/etype/atf_fip.py
new file mode 100644
index 0000000..07e6c64
--- /dev/null
+++ b/tools/binman/etype/atf_fip.py
@@ -0,0 +1,273 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2019 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for the ARM Trusted Firmware's Firmware Image Package (FIP)
+# format
+
+from collections import OrderedDict
+
+from binman.entry import Entry
+from binman.etype.section import Entry_section
+from binman.fip_util import FIP_TYPES, FipReader, FipWriter, UUID_LEN
+from dtoc import fdt_util
+from patman import tools
+
+class Entry_atf_fip(Entry_section):
+ """ARM Trusted Firmware's Firmware Image Package (FIP)
+
+ A FIP_ provides a way to group binaries in a firmware image, used by ARM's
+ Trusted Firmware A (TF-A) code. It is a simple format consisting of a
+ table of contents with information about the type, offset and size of the
+ binaries in the FIP. It is quite similar to FMAP, with the major difference
+ that it uses UUIDs to indicate the type of each entry.
+
+ Note: It is recommended to always add an fdtmap to every image, as well as
+ any FIPs so that binman and other tools can access the entire image
+ correctly.
+
+ The UUIDs correspond to useful names in `fiptool`, provided by ATF to
+ operate on FIPs. Binman uses these names to make it easier to understand
+ what is going on, although it is possible to provide a UUID if needed.
+
+ The contents of the FIP are defined by subnodes of the atf-fip entry, e.g.::
+
+ atf-fip {
+ soc-fw {
+ filename = "bl31.bin";
+ };
+
+ scp-fwu-cfg {
+ filename = "bl2u.bin";
+ };
+
+ u-boot {
+ fip-type = "nt-fw";
+ };
+ };
+
+ This describes a FIP with three entries: soc-fw, scp-fwu-cfg and nt-fw.
+ You can use normal (non-external) binaries like U-Boot simply by adding a
+ FIP type, with the `fip-type` property, as above.
+
+ Since FIP exists to bring blobs together, Binman assumes that all FIP
+ entries are external binaries. If a binary may not exist, you can use the
+ `--allow-missing` flag to Binman, in which case the image is still created,
+ even though it will not actually work.
+
+ The size of the FIP depends on the size of the binaries. There is currently
+ no way to specify a fixed size. If the `atf-fip` node has a `size` entry,
+ this affects the space taken up by the `atf-fip` entry, but the FIP itself
+ does not expand to use that space.
+
+ Some other FIP features are available with Binman. The header and the
+ entries have 64-bit flag works. The flag flags do not seem to be defined
+ anywhere, but you can use `fip-hdr-flags` and fip-flags` to set the values
+ of the header and entries respectively.
+
+ FIP entries can be aligned to a particular power-of-two boundary. Use
+ fip-align for this.
+
+ Binman only understands the entry types that are included in its
+ implementation. It is possible to specify a 16-byte UUID instead, using the
+ fip-uuid property. In this case Binman doesn't know what its type is, so
+ just uses the UUID. See the `u-boot` node in this example::
+
+ binman {
+ atf-fip {
+ fip-hdr-flags = /bits/ 64 <0x123>;
+ fip-align = <16>;
+ soc-fw {
+ fip-flags = /bits/ 64 <0x456>;
+ filename = "bl31.bin";
+ };
+
+ scp-fwu-cfg {
+ filename = "bl2u.bin";
+ };
+
+ u-boot {
+ fip-uuid = [fc 65 13 92 4a 5b 11 ec
+ 94 35 ff 2d 1c fc 79 9c];
+ };
+ };
+ fdtmap {
+ };
+ };
+
+ Binman allows reading and updating FIP entries after the image is created,
+ provided that an FDPMAP is present too. Updates which change the size of a
+ FIP entry will cause it to be expanded or contracted as needed.
+
+ Properties for top-level atf-fip node
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ fip-hdr-flags (64 bits)
+ Sets the flags for the FIP header.
+
+ Properties for subnodes
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ fip-type (str)
+ FIP type to use for this entry. This is needed if the entry
+ name is not a valid type. Value types are defined in `fip_util.py`.
+ The FIP type defines the UUID that is used (they map 1:1).
+
+ fip-uuid (16 bytes)
+ If there is no FIP-type name defined, or it is not supported by Binman,
+ this property sets the UUID. It should be a 16-byte value, following the
+ hex digits of the UUID.
+
+ fip-flags (64 bits)
+ Set the flags for a FIP entry. Use in one of the subnodes of the
+ 7atf-fip entry.
+
+ fip-align
+ Set the alignment for a FIP entry, FIP entries can be aligned to a
+ particular power-of-two boundary. The default is 1.
+
+ Adding new FIP-entry types
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ When new FIP entries are defined by TF-A they appear in the
+ `TF-A source tree`_. You can use `fip_util.py` to update Binman to support
+ new types, then `send a patch`_ to the U-Boot mailing list. There are two
+ source files that the tool examples:
+
+ - `include/tools_share/firmware_image_package.h` has the UUIDs
+ - `tools/fiptool/tbbr_config.c` has the name and descripion for each UUID
+
+ To run the tool::
+
+ $ tools/binman/fip_util.py -s /path/to/arm-trusted-firmware
+ Warning: UUID 'UUID_NON_TRUSTED_WORLD_KEY_CERT' is not mentioned in tbbr_config.c file
+ Existing code in 'tools/binman/fip_util.py' is up-to-date
+
+ If it shows there is an update, it writes a new version of `fip_util.py`
+ to `fip_util.py.out`. You can change the output file using the `-i` flag.
+ If you have a problem, use `-D` to enable traceback debugging.
+
+ FIP commentary
+ ~~~~~~~~~~~~~~
+
+ As a side effect of use of UUIDs, FIP does not support multiple
+ entries of the same type, such as might be used to store fonts or graphics
+ icons, for example. For verified boot it could be used for each part of the
+ image (e.g. separate FIPs for A and B) but cannot describe the whole
+ firmware image. As with FMAP there is no hierarchy defined, although FMAP
+ works around this by having 'section' areas which encompass others. A
+ similar workaround would be possible with FIP but is not currently defined.
+
+ It is recommended to always add an fdtmap to every image, as well as any
+ FIPs so that binman and other tools can access the entire image correctly.
+
+ .. _FIP: https://trustedfirmware-a.readthedocs.io/en/latest/design/firmware-design.html#firmware-image-package-fip
+ .. _`TF-A source tree`: https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git
+ .. _`send a patch`: https://www.denx.de/wiki/U-Boot/Patches
+ """
+ def __init__(self, section, etype, node):
+ # Put this here to allow entry-docs and help to work without libfdt
+ global state
+ from binman import state
+
+ super().__init__(section, etype, node)
+ self.align_default = None
+ self._entries = OrderedDict()
+ self.reader = None
+
+ def ReadNode(self):
+ """Read properties from the atf-fip node"""
+ super().ReadNode()
+ self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
+ self._fip_flags = fdt_util.GetInt64(self._node, 'fip-hdr-flags', 0)
+ self._fip_align = fdt_util.GetInt(self._node, 'fip-align', 1)
+ if tools.NotPowerOfTwo(self._fip_align):
+ raise ValueError("Node '%s': FIP alignment %s must be a power of two" %
+ (self._node.path, self._fip_align))
+ self.ReadEntries()
+
+ def ReadEntries(self):
+ """Read the subnodes to find out what should go in this FIP"""
+ for node in self._node.subnodes:
+ fip_type = None
+ etype = None
+ if node.name in FIP_TYPES:
+ fip_type = node.name
+ etype = 'blob-ext'
+
+ entry = Entry.Create(self, node, etype)
+ entry._fip_uuid = fdt_util.GetBytes(node, 'fip-uuid', UUID_LEN)
+ if not fip_type and not entry._fip_uuid:
+ fip_type = fdt_util.GetString(node, 'fip-type')
+ if not fip_type:
+ self.Raise("Must provide a fip-type (node name '%s' is not a known FIP type)" %
+ node.name)
+
+ entry._fip_type = fip_type
+ entry._fip_flags = fdt_util.GetInt64(node, 'fip-flags', 0)
+ entry.ReadNode()
+ entry._fip_name = node.name
+ self._entries[entry._fip_name] = entry
+
+ def BuildSectionData(self, required):
+ """Override this function to create a custom format for the entries
+
+ Arguments:
+ required (bool): True if the data must be valid, False if it may
+ be missing (entry.GetData() returns None
+
+ Returns:
+ bytes: Data obtained, or None if None
+ """
+ fip = FipWriter(self._fip_flags, self._fip_align)
+ for entry in self._entries.values():
+ # First get the input data and put it in an entry. If not available,
+ # try later.
+ entry_data = entry.GetData(required)
+ if not required and entry_data is None:
+ return None
+ fent = fip.add_entry(entry._fip_type or entry._fip_uuid, entry_data,
+ entry._fip_flags)
+ if fent:
+ entry._fip_entry = fent
+ data = fip.get_data()
+ return data
+
+ def SetImagePos(self, image_pos):
+ """Override this function to set all the entry properties from FIP
+
+ We can only do this once image_pos is known
+
+ Args:
+ image_pos: Position of this entry in the image
+ """
+ super().SetImagePos(image_pos)
+
+ # Now update the entries with info from the FIP entries
+ for entry in self._entries.values():
+ fent = entry._fip_entry
+ entry.size = fent.size
+ entry.offset = fent.offset
+ entry.image_pos = self.image_pos + entry.offset
+
+ def ReadChildData(self, child, decomp=True, alt_format=None):
+ if not self.reader:
+ self.fip_data = super().ReadData(True)
+ self.reader = FipReader(self.fip_data)
+ reader = self.reader
+
+ # It is tricky to obtain the data from a FIP entry since it is indexed
+ # by its UUID.
+ fent = reader.get_entry(child._fip_type or child._fip_uuid)
+ return fent.data
+
+ # Note:
+ # It is also possible to extract it using the offsets directly, but this
+ # seems less FIP_friendly:
+ # return self.fip_data[child.offset:child.offset + child.size]
+
+ def WriteChildData(self, child):
+ # Recreate the data structure, leaving the data for this child alone,
+ # so that child.data is used to pack into the FIP.
+ self.ObtainContents(skip_entry=child)
+ return True
diff --git a/tools/binman/etype/blob.py b/tools/binman/etype/blob.py
index fae86ca..8c1b809 100644
--- a/tools/binman/etype/blob.py
+++ b/tools/binman/etype/blob.py
@@ -48,10 +48,10 @@ class Entry_blob(Entry):
self.ReadBlobContents()
return True
- def ReadBlobContents(self):
+ def ReadFileContents(self, pathname):
"""Read blob contents into memory
- This function compresses the data before storing if needed.
+ This function compresses the data before returning if needed.
We assume the data is small enough to fit into memory. If this
is used for large filesystem image that might not be true.
@@ -59,13 +59,23 @@ class Entry_blob(Entry):
new Entry method which can read in chunks. Then we could copy
the data in chunks and avoid reading it all at once. For now
this seems like an unnecessary complication.
+
+ Args:
+ pathname (str): Pathname to read from
+
+ Returns:
+ bytes: Data read
"""
state.TimingStart('read')
- indata = tools.ReadFile(self._pathname)
+ indata = tools.ReadFile(pathname)
state.TimingAccum('read')
state.TimingStart('compress')
data = self.CompressData(indata)
state.TimingAccum('compress')
+ return data
+
+ def ReadBlobContents(self):
+ data = self.ReadFileContents(self._pathname)
self.SetContents(data)
return True
diff --git a/tools/binman/etype/blob_ext_list.py b/tools/binman/etype/blob_ext_list.py
new file mode 100644
index 0000000..136ae81
--- /dev/null
+++ b/tools/binman/etype/blob_ext_list.py
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for a list of external blobs, not built by U-Boot
+#
+
+import os
+
+from binman.etype.blob import Entry_blob
+from dtoc import fdt_util
+from patman import tools
+from patman import tout
+
+class Entry_blob_ext_list(Entry_blob):
+ """List of externally built binary blobs
+
+ This is like blob-ext except that a number of blobs can be provided,
+ typically with some sort of relationship, e.g. all are DDC parameters.
+
+ If any of the external files needed by this llist is missing, binman can
+ optionally ignore it and produce a broken image with a warning.
+
+ Args:
+ filenames: List of filenames to read and include
+ """
+ def __init__(self, section, etype, node):
+ Entry_blob.__init__(self, section, etype, node)
+ self.external = True
+
+ def ReadNode(self):
+ super().ReadNode()
+ self._filenames = fdt_util.GetStringList(self._node, 'filenames')
+ self._pathnames = []
+
+ def ObtainContents(self):
+ missing = False
+ pathnames = []
+ for fname in self._filenames:
+ pathname = tools.GetInputFilename(
+ fname, self.external and self.section.GetAllowMissing())
+ # Allow the file to be missing
+ if not pathname:
+ missing = True
+ pathnames.append(pathname)
+ self._pathnames = pathnames
+
+ if missing:
+ self.SetContents(b'')
+ self.missing = True
+ return True
+
+ data = bytearray()
+ for pathname in pathnames:
+ data += self.ReadFileContents(pathname)
+
+ self.SetContents(data)
+ return True
diff --git a/tools/binman/etype/blob_phase.py b/tools/binman/etype/blob_phase.py
index 54ca54c..ed25e46 100644
--- a/tools/binman/etype/blob_phase.py
+++ b/tools/binman/etype/blob_phase.py
@@ -48,4 +48,4 @@ class Entry_blob_phase(Entry_section):
subnode = state.AddSubnode(self._node, name)
# Read entries again, now that we have some
- self._ReadEntries()
+ self.ReadEntries()
diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py
index 44db7b9..cc1fbdf 100644
--- a/tools/binman/etype/cbfs.py
+++ b/tools/binman/etype/cbfs.py
@@ -168,43 +168,17 @@ class Entry_cbfs(Entry):
from binman import state
super().__init__(section, etype, node)
- self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86')
self.align_default = None
- self._cbfs_entries = OrderedDict()
- self._ReadSubnodes()
+ self._entries = OrderedDict()
self.reader = None
- def ObtainContents(self, skip=None):
- arch = cbfs_util.find_arch(self._cbfs_arg)
- if arch is None:
- self.Raise("Invalid architecture '%s'" % self._cbfs_arg)
- if self.size is None:
- self.Raise("'cbfs' entry must have a size property")
- cbfs = CbfsWriter(self.size, arch)
- for entry in self._cbfs_entries.values():
- # First get the input data and put it in a file. If not available,
- # try later.
- if entry != skip and not entry.ObtainContents():
- return False
- data = entry.GetData()
- cfile = None
- if entry._type == 'raw':
- cfile = cbfs.add_file_raw(entry._cbfs_name, data,
- entry._cbfs_offset,
- entry._cbfs_compress)
- elif entry._type == 'stage':
- cfile = cbfs.add_file_stage(entry._cbfs_name, data,
- entry._cbfs_offset)
- else:
- entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" %
- entry._type)
- if cfile:
- entry._cbfs_file = cfile
- data = cbfs.get_data()
- self.SetContents(data)
- return True
+ def ReadNode(self):
+ """Read properties from the atf-fip node"""
+ super().ReadNode()
+ self._cbfs_arg = fdt_util.GetString(self._node, 'cbfs-arch', 'x86')
+ self.ReadEntries()
- def _ReadSubnodes(self):
+ def ReadEntries(self):
"""Read the subnodes to find out what should go in this CBFS"""
for node in self._node.subnodes:
entry = Entry.Create(self, node)
@@ -217,7 +191,41 @@ class Entry_cbfs(Entry):
if entry._cbfs_compress is None:
self.Raise("Invalid compression in '%s': '%s'" %
(node.name, compress))
- self._cbfs_entries[entry._cbfs_name] = entry
+ self._entries[entry._cbfs_name] = entry
+
+ def ObtainCfile(self, cbfs, entry):
+ # First get the input data and put it in a file. If not available,
+ # try later.
+ data = entry.GetData()
+ cfile = None
+ if entry._type == 'raw':
+ cfile = cbfs.add_file_raw(entry._cbfs_name, data,
+ entry._cbfs_offset,
+ entry._cbfs_compress)
+ elif entry._type == 'stage':
+ cfile = cbfs.add_file_stage(entry._cbfs_name, data,
+ entry._cbfs_offset)
+ else:
+ entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" %
+ entry._type)
+ return cfile
+
+ def ObtainContents(self, skip_entry=None):
+ arch = cbfs_util.find_arch(self._cbfs_arg)
+ if arch is None:
+ self.Raise("Invalid architecture '%s'" % self._cbfs_arg)
+ if self.size is None:
+ self.Raise("'cbfs' entry must have a size property")
+ cbfs = CbfsWriter(self.size, arch)
+ for entry in self._entries.values():
+ if entry != skip_entry and not entry.ObtainContents():
+ return False
+ cfile = self.ObtainCfile(cbfs, entry)
+ if cfile:
+ entry._cbfs_file = cfile
+ data = cbfs.get_data()
+ self.SetContents(data)
+ return True
def SetImagePos(self, image_pos):
"""Override this function to set all the entry properties from CBFS
@@ -230,7 +238,7 @@ class Entry_cbfs(Entry):
super().SetImagePos(image_pos)
# Now update the entries with info from the CBFS entries
- for entry in self._cbfs_entries.values():
+ for entry in self._entries.values():
cfile = entry._cbfs_file
entry.size = cfile.data_len
entry.offset = cfile.calced_cbfs_offset
@@ -240,7 +248,7 @@ class Entry_cbfs(Entry):
def AddMissingProperties(self, have_image_pos):
super().AddMissingProperties(have_image_pos)
- for entry in self._cbfs_entries.values():
+ for entry in self._entries.values():
entry.AddMissingProperties(have_image_pos)
if entry._cbfs_compress:
state.AddZeroProp(entry._node, 'uncomp-size')
@@ -252,7 +260,7 @@ class Entry_cbfs(Entry):
def SetCalculatedProperties(self):
"""Set the value of device-tree properties calculated by binman"""
super().SetCalculatedProperties()
- for entry in self._cbfs_entries.values():
+ for entry in self._entries.values():
state.SetInt(entry._node, 'offset', entry.offset)
state.SetInt(entry._node, 'size', entry.size)
state.SetInt(entry._node, 'image-pos', entry.image_pos)
@@ -262,24 +270,26 @@ class Entry_cbfs(Entry):
def ListEntries(self, entries, indent):
"""Override this method to list all files in the section"""
super().ListEntries(entries, indent)
- for entry in self._cbfs_entries.values():
+ for entry in self._entries.values():
entry.ListEntries(entries, indent + 1)
def GetEntries(self):
- return self._cbfs_entries
+ return self._entries
- def ReadData(self, decomp=True):
- data = super().ReadData(True)
+ def ReadData(self, decomp=True, alt_format=None):
+ data = super().ReadData(True, alt_format)
return data
- def ReadChildData(self, child, decomp=True):
+ def ReadChildData(self, child, decomp=True, alt_format=None):
if not self.reader:
- data = super().ReadData(True)
+ data = super().ReadData(True, alt_format)
self.reader = cbfs_util.CbfsReader(data)
reader = self.reader
cfile = reader.files.get(child.name)
return cfile.data if decomp else cfile.orig_data
def WriteChildData(self, child):
- self.ObtainContents(skip=child)
+ # Recreate the data structure, leaving the data for this child alone,
+ # so that child.data is used to pack into the FIP.
+ self.ObtainContents(skip_entry=child)
return True
diff --git a/tools/binman/etype/fdtmap.py b/tools/binman/etype/fdtmap.py
index 2339fee..aaaf2de 100644
--- a/tools/binman/etype/fdtmap.py
+++ b/tools/binman/etype/fdtmap.py
@@ -74,6 +74,10 @@ class Entry_fdtmap(Entry):
If allow-repack is used then 'orig-offset' and 'orig-size' properties are
added as necessary. See the binman README.
+
+ When extracting files, an alternative 'fdt' format is available for fdtmaps.
+ Use `binman extract -F fdt ...` to use this. It will export a devicetree,
+ without the fdtmap header, so it can be viewed with `fdtdump`.
"""
def __init__(self, section, etype, node):
# Put these here to allow entry-docs and help to work without libfdt
@@ -86,6 +90,10 @@ class Entry_fdtmap(Entry):
from dtoc.fdt import Fdt
super().__init__(section, etype, node)
+ self.alt_formats = ['fdt']
+
+ def CheckAltFormats(self, alt_formats):
+ alt_formats['fdt'] = self, 'Extract the devicetree blob from the fdtmap'
def _GetFdtmap(self):
"""Build an FDT map from the entries in the current image
@@ -147,3 +155,7 @@ class Entry_fdtmap(Entry):
processing, e.g. the image-pos properties.
"""
return self.ProcessContentsUpdate(self._GetFdtmap())
+
+ def GetAltFormat(self, data, alt_format):
+ if alt_format == 'fdt':
+ return data[FDTMAP_HDR_LEN:]
diff --git a/tools/binman/etype/files.py b/tools/binman/etype/files.py
index 9b04a49..927d0f0 100644
--- a/tools/binman/etype/files.py
+++ b/tools/binman/etype/files.py
@@ -64,4 +64,4 @@ class Entry_files(Entry_section):
state.AddInt(subnode, 'align', self._files_align)
# Read entries again, now that we have some
- self._ReadEntries()
+ self.ReadEntries()
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 6936f57..b41187d 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -136,10 +136,10 @@ class Entry_fit(Entry):
str)])[0]
def ReadNode(self):
- self._ReadSubnodes()
+ self.ReadEntries()
super().ReadNode()
- def _ReadSubnodes(self):
+ def ReadEntries(self):
def _AddNode(base_node, depth, node):
"""Add a node to the FIT
diff --git a/tools/binman/etype/intel_ifwi.py b/tools/binman/etype/intel_ifwi.py
index 903d39b..ecbd78d 100644
--- a/tools/binman/etype/intel_ifwi.py
+++ b/tools/binman/etype/intel_ifwi.py
@@ -50,7 +50,7 @@ class Entry_intel_ifwi(Entry_blob_ext):
self._ifwi_entries = OrderedDict()
def ReadNode(self):
- self._ReadSubnodes()
+ self.ReadEntries()
super().ReadNode()
def _BuildIfwi(self):
@@ -117,7 +117,7 @@ class Entry_intel_ifwi(Entry_blob_ext):
same = orig_data == self.data
return same
- def _ReadSubnodes(self):
+ def ReadEntries(self):
"""Read the subnodes to find out what should go in this IFWI"""
for node in self._node.subnodes:
entry = Entry.Create(self.section, node)
diff --git a/tools/binman/etype/mkimage.py b/tools/binman/etype/mkimage.py
index e499775..c08fd9d 100644
--- a/tools/binman/etype/mkimage.py
+++ b/tools/binman/etype/mkimage.py
@@ -37,7 +37,7 @@ class Entry_mkimage(Entry):
self._args = fdt_util.GetString(self._node, 'args').split(' ')
self._mkimage_entries = OrderedDict()
self.align_default = None
- self._ReadSubnodes()
+ self.ReadEntries()
def ObtainContents(self):
data = b''
@@ -55,7 +55,7 @@ class Entry_mkimage(Entry):
self.SetContents(tools.ReadFile(output_fname))
return True
- def _ReadSubnodes(self):
+ def ReadEntries(self):
"""Read the subnodes to find out what should go in this image"""
for node in self._node.subnodes:
entry = Entry.Create(self, node)
diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index e2949fc..43436a1 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -24,34 +24,135 @@ from patman.tools import ToHexSize
class Entry_section(Entry):
"""Entry that contains other entries
- Properties / Entry arguments: (see binman README for more information):
- pad-byte: Pad byte to use when padding
- sort-by-offset: True if entries should be sorted by offset, False if
- they must be in-order in the device tree description
-
- end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
-
- skip-at-start: Number of bytes before the first entry starts. These
- effectively adjust the starting offset of entries. For example,
- if this is 16, then the first entry would start at 16. An entry
- with offset = 20 would in fact be written at offset 4 in the image
- file, since the first 16 bytes are skipped when writing.
- name-prefix: Adds a prefix to the name of every entry in the section
- when writing out the map
- align_default: Default alignment for this section, if no alignment is
- given in the entry
-
- Properties:
- allow_missing: True if this section permits external blobs to be
- missing their contents. The second will produce an image but of
- course it will not work.
+ A section is an entry which can contain other entries, thus allowing
+ hierarchical images to be created. See 'Sections and hierarchical images'
+ in the binman README for more information.
+
+ The base implementation simply joins the various entries together, using
+ various rules about alignment, etc.
+
+ Subclassing
+ ~~~~~~~~~~~
+
+ This class can be subclassed to support other file formats which hold
+ multiple entries, such as CBFS. To do this, override the following
+ functions. The documentation here describes what your function should do.
+ For example code, see etypes which subclass `Entry_section`, or `cbfs.py`
+ for a more involved example::
+
+ $ grep -l \(Entry_section tools/binman/etype/*.py
+
+ ReadNode()
+ Call `super().ReadNode()`, then read any special properties for the
+ section. Then call `self.ReadEntries()` to read the entries.
+
+ Binman calls this at the start when reading the image description.
+
+ ReadEntries()
+ Read in the subnodes of the section. This may involve creating entries
+ of a particular etype automatically, as well as reading any special
+ properties in the entries. For each entry, entry.ReadNode() should be
+ called, to read the basic entry properties. The properties should be
+ added to `self._entries[]`, in the correct order, with a suitable name.
+
+ Binman calls this at the start when reading the image description.
+
+ BuildSectionData(required)
+ Create the custom file format that you want and return it as bytes.
+ This likely sets up a file header, then loops through the entries,
+ adding them to the file. For each entry, call `entry.GetData()` to
+ obtain the data. If that returns None, and `required` is False, then
+ this method must give up and return None. But if `required` is True then
+ it should assume that all data is valid.
+
+ Binman calls this when packing the image, to find out the size of
+ everything. It is called again at the end when building the final image.
+
+ SetImagePos(image_pos):
+ Call `super().SetImagePos(image_pos)`, then set the `image_pos` values
+ for each of the entries. This should use the custom file format to find
+ the `start offset` (and `image_pos`) of each entry. If the file format
+ uses compression in such a way that there is no offset available (other
+ than reading the whole file and decompressing it), then the offsets for
+ affected entries can remain unset (`None`). The size should also be set
+ if possible.
+
+ Binman calls this after the image has been packed, to update the
+ location that all the entries ended up at.
+
+ ReadChildData(child, decomp, alt_format):
+ The default version of this may be good enough, if you are able to
+ implement SetImagePos() correctly. But that is a bit of a bypass, so
+ you can override this method to read from your custom file format. It
+ should read the entire entry containing the custom file using
+ `super().ReadData(True)`, then parse the file to get the data for the
+ given child, then return that data.
+
+ If your file format supports compression, the `decomp` argument tells
+ you whether to return the compressed data (`decomp` is False) or to
+ uncompress it first, then return the uncompressed data (`decomp` is
+ True). This is used by the `binman extract -U` option.
+
+ If your entry supports alternative formats, the alt_format provides the
+ alternative format that the user has selected. Your function should
+ return data in that format. This is used by the 'binman extract -l'
+ option.
+
+ Binman calls this when reading in an image, in order to populate all the
+ entries with the data from that image (`binman ls`).
+
+ WriteChildData(child):
+ Binman calls this after `child.data` is updated, to inform the custom
+ file format about this, in case it needs to do updates.
+
+ The default version of this does nothing and probably needs to be
+ overridden for the 'binman replace' command to work. Your version should
+ use `child.data` to update the data for that child in the custom file
+ format.
+
+ Binman calls this when updating an image that has been read in and in
+ particular to update the data for a particular entry (`binman replace`)
+
+ Properties / Entry arguments
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ See :ref:`develop/package/binman:Image description format` for more
+ information.
+
+ align-default
+ Default alignment for this section, if no alignment is given in the
+ entry
+
+ pad-byte
+ Pad byte to use when padding
+
+ sort-by-offset
+ True if entries should be sorted by offset, False if they must be
+ in-order in the device tree description
+
+ end-at-4gb
+ Used to build an x86 ROM which ends at 4GB (2^32)
+
+ name-prefix
+ Adds a prefix to the name of every entry in the section when writing out
+ the map
+
+ skip-at-start
+ Number of bytes before the first entry starts. These effectively adjust
+ the starting offset of entries. For example, if this is 16, then the
+ first entry would start at 16. An entry with offset = 20 would in fact
+ be written at offset 4 in the image file, since the first 16 bytes are
+ skipped when writing.
Since a section is also an entry, it inherits all the properies of entries
too.
- A section is an entry which can contain other entries, thus allowing
- hierarchical images to be created. See 'Sections and hierarchical images'
- in the binman README for more information.
+ Note that the `allow_missing` member controls whether this section permits
+ external blobs to be missing their contents. The option will produce an
+ image but of course it will not work. It is useful to make sure that
+ Continuous Integration systems can build without the binaries being
+ available. This is set by the `SetAllowMissing()` method, if
+ `--allow-missing` is passed to binman.
"""
def __init__(self, section, etype, node, test=False):
if not test:
@@ -81,18 +182,16 @@ class Entry_section(Entry):
self._skip_at_start = 0
self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
self.align_default = fdt_util.GetInt(self._node, 'align-default', 0)
- filename = fdt_util.GetString(self._node, 'filename')
- if filename:
- self._filename = filename
- self._ReadEntries()
+ self.ReadEntries()
- def _ReadEntries(self):
+ def ReadEntries(self):
for node in self._node.subnodes:
if node.name.startswith('hash') or node.name.startswith('signature'):
continue
entry = Entry.Create(self, node,
- expanded=self.GetImage().use_expanded)
+ expanded=self.GetImage().use_expanded,
+ missing_etype=self.GetImage().missing_etype)
entry.ReadNode()
entry.SetPrefix(self._name_prefix)
self._entries[node.name] = entry
@@ -101,9 +200,9 @@ class Entry_section(Entry):
"""Raises an error for this section
Args:
- msg: Error message to use in the raise string
+ msg (str): Error message to use in the raise string
Raises:
- ValueError()
+ ValueError: always
"""
raise ValueError("Section '%s': %s" % (self._node.path, msg))
@@ -146,8 +245,8 @@ class Entry_section(Entry):
for entry in self._entries.values():
entry.AddMissingProperties(have_image_pos)
- def ObtainContents(self):
- return self.GetEntryContents()
+ def ObtainContents(self, skip_entry=None):
+ return self.GetEntryContents(skip_entry=skip_entry)
def GetPaddedDataForEntry(self, entry, entry_data):
"""Get the data for an entry including any padding
@@ -185,7 +284,7 @@ class Entry_section(Entry):
return data
- def _BuildSectionData(self, required):
+ def BuildSectionData(self, required):
"""Build the contents of a section
This places all entries at the right place, dealing with padding before
@@ -193,6 +292,9 @@ class Entry_section(Entry):
pad-before and pad-after properties in the section items) since that is
handled by the parent section.
+ This should be overridden by subclasses which want to build their own
+ data structure for the section.
+
Args:
required: True if the data must be present, False if it is OK to
return None
@@ -204,6 +306,9 @@ class Entry_section(Entry):
for entry in self._entries.values():
entry_data = entry.GetData(required)
+
+ # This can happen when this section is referenced from a collection
+ # earlier in the image description. See testCollectionSection().
if not required and entry_data is None:
return None
data = self.GetPaddedDataForEntry(entry, entry_data)
@@ -253,7 +358,7 @@ class Entry_section(Entry):
This excludes any padding. If the section is compressed, the
compressed data is returned
"""
- data = self._BuildSectionData(required)
+ data = self.BuildSectionData(required)
if data is None:
return None
self.SetContents(data)
@@ -281,7 +386,7 @@ class Entry_section(Entry):
self._SortEntries()
self._ExpandEntries()
- data = self._BuildSectionData(True)
+ data = self.BuildSectionData(True)
self.SetContents(data)
self.CheckSize()
@@ -524,12 +629,13 @@ class Entry_section(Entry):
return entry
return None
- def GetEntryContents(self):
+ def GetEntryContents(self, skip_entry=None):
"""Call ObtainContents() for each entry in the section
"""
def _CheckDone(entry):
- if not entry.ObtainContents():
- next_todo.append(entry)
+ if entry != skip_entry:
+ if not entry.ObtainContents():
+ next_todo.append(entry)
return entry
todo = self._entries.values()
@@ -617,7 +723,7 @@ class Entry_section(Entry):
def ListEntries(self, entries, indent):
"""List the files in the section"""
- Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
+ Entry.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
self.image_pos, None, self.offset, self)
for entry in self._entries.values():
entry.ListEntries(entries, indent + 1)
@@ -649,9 +755,9 @@ class Entry_section(Entry):
"""
return self._sort
- def ReadData(self, decomp=True):
+ def ReadData(self, decomp=True, alt_format=None):
tout.Info("ReadData path='%s'" % self.GetPath())
- parent_data = self.section.ReadData(True)
+ parent_data = self.section.ReadData(True, alt_format)
offset = self.offset - self.section._skip_at_start
data = parent_data[offset:offset + self.size]
tout.Info(
@@ -660,9 +766,9 @@ class Entry_section(Entry):
self.size, len(data)))
return data
- def ReadChildData(self, child, decomp=True):
- tout.Debug("ReadChildData for child '%s'" % child.GetPath())
- parent_data = self.ReadData(True)
+ def ReadChildData(self, child, decomp=True, alt_format=None):
+ tout.Debug(f"ReadChildData for child '{child.GetPath()}'")
+ parent_data = self.ReadData(True, alt_format)
offset = child.offset - self._skip_at_start
tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
(child.GetPath(), child.offset, self._skip_at_start, offset))
@@ -674,6 +780,10 @@ class Entry_section(Entry):
tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
(child.GetPath(), len(indata), child.compress,
len(data)))
+ if alt_format:
+ new_data = child.GetAltFormat(data, alt_format)
+ if new_data is not None:
+ data = new_data
return data
def WriteChildData(self, child):
@@ -738,8 +848,14 @@ class Entry_section(Entry):
nothing.
Args:
- missing: List of missing properties / entry args, each a string
+ entry (Entry): Entry to raise the error on
+ missing (list of str): List of missing properties / entry args, each
+ a string
"""
if not self._ignore_missing:
- entry.Raise('Missing required properties/entry args: %s' %
- (', '.join(missing)))
+ missing = ', '.join(missing)
+ entry.Raise(f'Missing required properties/entry args: {missing}')
+
+ def CheckAltFormats(self, alt_formats):
+ for entry in self._entries.values():
+ entry.CheckAltFormats(alt_formats)