blob: cd2943533ce2c625c3b95efb8c9c083c76a3d1a4 [file] [log] [blame]
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2016 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
"""Entry-type module for producing a FIT"""
import libfdt
from binman.entry import Entry, EntryArg
from binman.etype.section import Entry_section
from binman import elf
from dtoc import fdt_util
from dtoc.fdt import Fdt
from patman import tools
# Supported operations, with the fit,operation property
OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
OPERATIONS = {
'gen-fdt-nodes': OP_GEN_FDT_NODES,
'split-elf': OP_SPLIT_ELF,
}
class Entry_fit(Entry_section):
"""Flat Image Tree (FIT)
This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
input provided.
Nodes for the FIT should be written out in the binman configuration just as
they would be in a file passed to mkimage.
For example, this creates an image containing a FIT with U-Boot SPL::
binman {
fit {
description = "Test FIT";
fit,fdt-list = "of-list";
images {
kernel@1 {
description = "SPL";
os = "u-boot";
type = "rkspi";
arch = "arm";
compression = "none";
load = <0>;
entry = <0>;
u-boot-spl {
};
};
};
};
};
More complex setups can be created, with generated nodes, as described
below.
Properties (in the 'fit' node itself)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Special properties have a `fit,` prefix, indicating that they should be
processed but not included in the final FIT.
The top-level 'fit' node supports the following special properties:
fit,external-offset
Indicates that the contents of the FIT are external and provides the
external offset. This is passed to mkimage via the -E and -p flags.
fit,align
Indicates what alignment to use for the FIT and its external data,
and provides the alignment to use. This is passed to mkimage via
the -B flag.
fit,fdt-list
Indicates the entry argument which provides the list of device tree
files for the gen-fdt-nodes operation (as below). This is often
`of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
to binman.
Substitutions
~~~~~~~~~~~~~
Node names and property values support a basic string-substitution feature.
Available substitutions for '@' nodes (and property values) are:
SEQ:
Sequence number of the generated fdt (1, 2, ...)
NAME
Name of the dtb as provided (i.e. without adding '.dtb')
The `default` property, if present, will be automatically set to the name
if of configuration whose devicetree matches the `default-dt` entry
argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
Available substitutions for property values in these nodes are:
DEFAULT-SEQ:
Sequence number of the default fdt, as provided by the 'default-dt'
entry argument
Available operations
~~~~~~~~~~~~~~~~~~~~
You can add an operation to an '@' node to indicate which operation is
required::
@fdt-SEQ {
fit,operation = "gen-fdt-nodes";
...
};
Available operations are:
gen-fdt-nodes
Generate FDT nodes as above. This is the default if there is no
`fit,operation` property.
split-elf
Split an ELF file into a separate node for each segment.
Generating nodes from an FDT list (gen-fdt-nodes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
U-Boot supports creating fdt and config nodes automatically. To do this,
pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
binman that you want to generates nodes for two files: `file1.dtb` and
`file2.dtb`. The `fit,fdt-list` property (see above) indicates that
`of-list` should be used. If the property is missing you will get an error.
Then add a 'generator node', a node with a name starting with '@'::
images {
@fdt-SEQ {
description = "fdt-NAME";
type = "flat_dt";
compression = "none";
};
};
This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
files. All the properties you specify will be included in the node. This
node acts like a template to generate the nodes. The generator node itself
does not appear in the output - it is replaced with what binman generates.
A 'data' property is created with the contents of the FDT file.
You can create config nodes in a similar way::
configurations {
default = "@config-DEFAULT-SEQ";
@config-SEQ {
description = "NAME";
firmware = "atf";
loadables = "uboot";
fdt = "fdt-SEQ";
};
};
This tells binman to create nodes `config-1` and `config-2`, i.e. a config
for each of your two files.
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
Generating nodes from an ELF file (split-elf)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This uses the node as a template to generate multiple nodes. The following
special properties are available:
split-elf
Split an ELF file into a separate node for each segment. This uses the
node as a template to generate multiple nodes. The following special
properties are available:
fit,load
Generates a `load = <...>` property with the load address of the
segment
fit,entry
Generates a `entry = <...>` property with the entry address of the
ELF. This is only produced for the first entry
fit,data
Generates a `data = <...>` property with the contents of the segment
fit,firmware
Generates a `firmware = <...>` property. Provides a list of possible
nodes to be used as the `firmware` property value. The first valid
node is picked as the firmware. Any remaining valid nodes is
prepended to the `loadable` property generated by `fit,loadables`
fit,loadables
Generates a `loadable = <...>` property with a list of the generated
nodes (including all nodes if this operation is used multiple times)
Here is an example showing ATF, TEE and a device tree all combined::
fit {
description = "test-desc";
#address-cells = <1>;
fit,fdt-list = "of-list";
images {
u-boot {
description = "U-Boot (64-bit)";
type = "standalone";
os = "U-Boot";
arch = "arm64";
compression = "none";
load = <CONFIG_TEXT_BASE>;
u-boot-nodtb {
};
};
@fdt-SEQ {
description = "fdt-NAME.dtb";
type = "flat_dt";
compression = "none";
};
@atf-SEQ {
fit,operation = "split-elf";
description = "ARM Trusted Firmware";
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
fit,load;
fit,entry;
fit,data;
atf-bl31 {
};
hash {
algo = "sha256";
};
};
@tee-SEQ {
fit,operation = "split-elf";
description = "TEE";
type = "tee";
arch = "arm64";
os = "tee";
compression = "none";
fit,load;
fit,entry;
fit,data;
tee-os {
};
hash {
algo = "sha256";
};
};
};
configurations {
default = "@config-DEFAULT-SEQ";
@config-SEQ {
description = "conf-NAME.dtb";
fdt = "fdt-SEQ";
fit,firmware = "atf-1", "u-boot";
fit,loadables;
};
};
};
If ATF-BL31 is available, this generates a node for each segment in the
ELF file, for example::
images {
atf-1 {
data = <...contents of first segment...>;
data-offset = <0x00000000>;
entry = <0x00040000>;
load = <0x00040000>;
compression = "none";
os = "arm-trusted-firmware";
arch = "arm64";
type = "firmware";
description = "ARM Trusted Firmware";
hash {
algo = "sha256";
value = <...hash of first segment...>;
};
};
atf-2 {
data = <...contents of second segment...>;
load = <0xff3b0000>;
compression = "none";
os = "arm-trusted-firmware";
arch = "arm64";
type = "firmware";
description = "ARM Trusted Firmware";
hash {
algo = "sha256";
value = <...hash of second segment...>;
};
};
};
The same applies for OP-TEE if that is available.
If each binary is not available, the relevant template node (@atf-SEQ or
@tee-SEQ) is removed from the output.
This also generates a `config-xxx` node for each device tree in `of-list`.
Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
so you can use `CONFIG_OF_LIST` to define that list. In this example it is
set up for `firefly-rk3399` with a single device tree and the default set
with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
is::
configurations {
default = "config-1";
config-1 {
loadables = "u-boot", "atf-2", "atf-3", "tee-1", "tee-2";
description = "rk3399-firefly.dtb";
fdt = "fdt-1";
firmware = "atf-1";
};
};
U-Boot SPL can then load the firmware (ATF) and all the loadables (U-Boot
proper, ATF and TEE), then proceed with the boot.
"""
def __init__(self, section, etype, node):
"""
Members:
_fit: FIT file being built
_entries: dict from Entry_section:
key: relative path to entry Node (from the base of the FIT)
value: Entry_section object comprising the contents of this
node
_priv_entries: Internal copy of _entries which includes 'generator'
entries which are used to create the FIT, but should not be
processed as real entries. This is set up once we have the
entries
_loadables: List of generated split-elf nodes, each a node name
"""
super().__init__(section, etype, node)
self._fit = None
self._fit_props = {}
self._fdts = None
self.mkimage = None
self._priv_entries = {}
self._loadables = []
def ReadNode(self):
super().ReadNode()
for pname, prop in self._node.props.items():
if pname.startswith('fit,'):
self._fit_props[pname] = prop
self._fit_list_prop = self._fit_props.get('fit,fdt-list')
if self._fit_list_prop:
fdts, = self.GetEntryArgsOrProps(
[EntryArg(self._fit_list_prop.value, str)])
if fdts is not None:
self._fdts = fdts.split()
self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
str)])[0]
def _get_operation(self, base_node, node):
"""Get the operation referenced by a subnode
Args:
node (Node): Subnode (of the FIT) to check
Returns:
int: Operation to perform
Raises:
ValueError: Invalid operation name
"""
oper_name = node.props.get('fit,operation')
if not oper_name:
return OP_GEN_FDT_NODES
oper = OPERATIONS.get(oper_name.value)
if oper is None:
self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
return oper
def ReadEntries(self):
def _add_entries(base_node, depth, node):
"""Add entries for any nodes that need them
Args:
base_node: Base Node of the FIT (with 'description' property)
depth: Current node depth (0 is the base 'fit' node)
node: Current node to process
Here we only need to provide binman entries which are used to define
the 'data' for each image. We create an entry_Section for each.
"""
rel_path = node.path[len(base_node.path):]
in_images = rel_path.startswith('/images')
has_images = depth == 2 and in_images
if has_images:
# This node is a FIT subimage node (e.g. "/images/kernel")
# containing content nodes. We collect the subimage nodes and
# section entries for them here to merge the content subnodes
# together and put the merged contents in the subimage node's
# 'data' property later.
entry = Entry.Create(self, node, etype='section')
entry.ReadNode()
# The hash subnodes here are for mkimage, not binman.
entry.SetUpdateHash(False)
image_name = rel_path[len('/images/'):]
self._entries[image_name] = entry
for subnode in node.subnodes:
_add_entries(base_node, depth + 1, subnode)
_add_entries(self._node, 0, self._node)
# Keep a copy of all entries, including generator entries, since those
# are removed from self._entries later.
self._priv_entries = dict(self._entries)
def BuildSectionData(self, required):
"""Build FIT entry contents
This adds the 'data' properties to the input ITB (Image-tree Binary)
then runs mkimage to process it.
Args:
required (bool): True if the data must be present, False if it is OK
to return None
Returns:
bytes: Contents of the section
"""
data = self._build_input()
uniq = self.GetUniqueName()
input_fname = tools.get_output_filename(f'{uniq}.itb')
output_fname = tools.get_output_filename(f'{uniq}.fit')
tools.write_file(input_fname, data)
tools.write_file(output_fname, data)
args = {}
ext_offset = self._fit_props.get('fit,external-offset')
if ext_offset is not None:
args = {
'external': True,
'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
}
align = self._fit_props.get('fit,align')
if align is not None:
args.update({'align': fdt_util.fdt32_to_cpu(align.value)})
if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
**args) is None:
# Bintool is missing; just use empty data as the output
self.record_missing_bintool(self.mkimage)
return tools.get_bytes(0, 1024)
return tools.read_file(output_fname)
def _raise_subnode(self, node, msg):
"""Raise an error with a paticular FIT subnode
Args:
node (Node): FIT subnode containing the error
msg (str): Message to report
Raises:
ValueError, as requested
"""
rel_path = node.path[len(self._node.path) + 1:]
self.Raise(f"subnode '{rel_path}': {msg}")
def _build_input(self):
"""Finish the FIT by adding the 'data' properties to it
Arguments:
fdt: FIT to update
Returns:
bytes: New fdt contents
"""
def _process_prop(pname, prop):
"""Process special properties
Handles properties with generated values. At present the only
supported property is 'default', i.e. the default device tree in
the configurations node.
Args:
pname (str): Name of property
prop (Prop): Property to process
"""
if pname == 'default':
val = prop.value
# Handle the 'default' property
if val.startswith('@'):
if not self._fdts:
return
if not self._fit_default_dt:
self.Raise("Generated 'default' node requires default-dt entry argument")
if self._fit_default_dt not in self._fdts:
self.Raise(
f"default-dt entry argument '{self._fit_default_dt}' "
f"not found in fdt list: {', '.join(self._fdts)}")
seq = self._fdts.index(self._fit_default_dt)
val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
fsw.property_string(pname, val)
return
elif pname.startswith('fit,'):
# Ignore these, which are commands for binman to process
return
elif pname in ['offset', 'size', 'image-pos']:
# Don't add binman's calculated properties
return
fsw.property(pname, prop.bytes)
def _process_firmware_prop(node):
"""Process optional fit,firmware property
Picks the first valid entry for use as the firmware, remaining valid
entries is prepended to loadables
Args:
node (Node): Generator node to process
Returns:
firmware (str): Firmware or None
result (list): List of remaining loadables
"""
val = fdt_util.GetStringList(node, 'fit,firmware')
if val is None:
return None, self._loadables
valid_entries = list(self._loadables)
for name, entry in self.GetEntries().items():
missing = []
entry.CheckMissing(missing)
entry.CheckOptional(missing)
if not missing:
valid_entries.append(name)
firmware = None
result = []
for name in val:
if name in valid_entries:
if not firmware:
firmware = name
elif name not in result:
result.append(name)
for name in self._loadables:
if name != firmware and name not in result:
result.append(name)
return firmware, result
def _gen_fdt_nodes(base_node, node, depth, in_images):
"""Generate FDT nodes
This creates one node for each member of self._fdts using the
provided template. If a property value contains 'NAME' it is
replaced with the filename of the FDT. If a property value contains
SEQ it is replaced with the node sequence number, where 1 is the
first.
Args:
node (Node): Generator node to process
depth: Current node depth (0 is the base 'fit' node)
in_images: True if this is inside the 'images' node, so that
'data' properties should be generated
"""
if self._fdts:
firmware, fit_loadables = _process_firmware_prop(node)
# Generate nodes for each FDT
for seq, fdt_fname in enumerate(self._fdts):
node_name = node.name[1:].replace('SEQ', str(seq + 1))
fname = tools.get_input_filename(fdt_fname + '.dtb')
with fsw.add_node(node_name):
for pname, prop in node.props.items():
if pname == 'fit,firmware':
if firmware:
fsw.property_string('firmware', firmware)
elif pname == 'fit,loadables':
val = '\0'.join(fit_loadables) + '\0'
fsw.property('loadables', val.encode('utf-8'))
elif pname == 'fit,operation':
pass
elif pname.startswith('fit,'):
self._raise_subnode(
node, f"Unknown directive '{pname}'")
else:
val = prop.bytes.replace(
b'NAME', tools.to_bytes(fdt_fname))
val = val.replace(
b'SEQ', tools.to_bytes(str(seq + 1)))
fsw.property(pname, val)
# Add data for 'images' nodes (but not 'config')
if depth == 1 and in_images:
fsw.property('data', tools.read_file(fname))
for subnode in node.subnodes:
with fsw.add_node(subnode.name):
_add_node(node, depth + 1, subnode)
else:
if self._fdts is None:
if self._fit_list_prop:
self.Raise('Generator node requires '
f"'{self._fit_list_prop.value}' entry argument")
else:
self.Raise("Generator node requires 'fit,fdt-list' property")
def _gen_split_elf(base_node, node, depth, segments, entry_addr):
"""Add nodes for the ELF file, one per group of contiguous segments
Args:
base_node (Node): Template node from the binman definition
node (Node): Node to replace (in the FIT being built)
depth: Current node depth (0 is the base 'fit' node)
segments (list): list of segments, each:
int: Segment number (0 = first)
int: Start address of segment in memory
bytes: Contents of segment
entry_addr (int): entry address of ELF file
"""
for (seq, start, data) in segments:
node_name = node.name[1:].replace('SEQ', str(seq + 1))
with fsw.add_node(node_name):
loadables.append(node_name)
for pname, prop in node.props.items():
if not pname.startswith('fit,'):
fsw.property(pname, prop.bytes)
elif pname == 'fit,load':
fsw.property_u32('load', start)
elif pname == 'fit,entry':
if seq == 0:
fsw.property_u32('entry', entry_addr)
elif pname == 'fit,data':
fsw.property('data', bytes(data))
elif pname != 'fit,operation':
self._raise_subnode(
node, f"Unknown directive '{pname}'")
for subnode in node.subnodes:
with fsw.add_node(subnode.name):
_add_node(node, depth + 1, subnode)
def _gen_node(base_node, node, depth, in_images, entry):
"""Generate nodes from a template
This creates one or more nodes depending on the fit,operation being
used.
For OP_GEN_FDT_NODES it creates one node for each member of
self._fdts using the provided template. If a property value contains
'NAME' it is replaced with the filename of the FDT. If a property
value contains SEQ it is replaced with the node sequence number,
where 1 is the first.
For OP_SPLIT_ELF it emits one node for each section in the ELF file.
If the file is missing, nothing is generated.
Args:
base_node (Node): Base Node of the FIT (with 'description'
property)
node (Node): Generator node to process
depth (int): Current node depth (0 is the base 'fit' node)
in_images (bool): True if this is inside the 'images' node, so
that 'data' properties should be generated
entry (entry_Section): Entry for the section containing the
contents of this node
"""
oper = self._get_operation(base_node, node)
if oper == OP_GEN_FDT_NODES:
_gen_fdt_nodes(base_node, node, depth, in_images)
elif oper == OP_SPLIT_ELF:
# Entry_section.ObtainContents() either returns True or
# raises an exception.
data = None
missing_opt_list = []
entry.ObtainContents()
entry.Pack(0)
entry.CheckMissing(missing_opt_list)
entry.CheckOptional(missing_opt_list)
# If any pieces are missing, skip this. The missing entries will
# show an error
if not missing_opt_list:
segs = entry.read_elf_segments()
if segs:
segments, entry_addr = segs
else:
elf_data = entry.GetData()
try:
segments, entry_addr = (
elf.read_loadable_segments(elf_data))
except ValueError as exc:
self._raise_subnode(
node, f'Failed to read ELF file: {str(exc)}')
_gen_split_elf(base_node, node, depth, segments, entry_addr)
def _add_node(base_node, depth, node):
"""Add nodes to the output FIT
Args:
base_node (Node): Base Node of the FIT (with 'description'
property)
depth (int): Current node depth (0 is the base 'fit' node)
node (Node): Current node to process
There are two cases to deal with:
- hash and signature nodes which become part of the FIT
- binman entries which are used to define the 'data' for each
image, so don't appear in the FIT
"""
# Copy over all the relevant properties
for pname, prop in node.props.items():
_process_prop(pname, prop)
rel_path = node.path[len(base_node.path):]
in_images = rel_path.startswith('/images')
has_images = depth == 2 and in_images
if has_images:
image_name = rel_path[len('/images/'):]
entry = self._priv_entries[image_name]
data = entry.GetData()
fsw.property('data', bytes(data))
for subnode in node.subnodes:
subnode_path = f'{rel_path}/{subnode.name}'
if has_images and not self.IsSpecialSubnode(subnode):
# This subnode is a content node not meant to appear in
# the FIT (e.g. "/images/kernel/u-boot"), so don't call
# fsw.add_node() or _add_node() for it.
pass
elif self.GetImage().generate and subnode.name.startswith('@'):
entry = self._priv_entries.get(subnode.name)
_gen_node(base_node, subnode, depth, in_images, entry)
# This is a generator (template) entry, so remove it from
# the list of entries used by PackEntries(), etc. Otherwise
# it will appear in the binman output
to_remove.append(subnode.name)
else:
with fsw.add_node(subnode.name):
_add_node(base_node, depth + 1, subnode)
# Build a new tree with all nodes and properties starting from the
# entry node
fsw = libfdt.FdtSw()
fsw.INC_SIZE = 65536
fsw.finish_reservemap()
to_remove = []
loadables = []
with fsw.add_node(''):
_add_node(self._node, 0, self._node)
self._loadables = loadables
fdt = fsw.as_fdt()
# Remove generator entries from the main list
for path in to_remove:
if path in self._entries:
del self._entries[path]
# Pack this new FDT and scan it so we can add the data later
fdt.pack()
data = fdt.as_bytearray()
return data
def SetImagePos(self, image_pos):
"""Set the position in the image
This sets each subentry's offsets, sizes and positions-in-image
according to where they ended up in the packed FIT file.
Args:
image_pos (int): Position of this entry in the image
"""
super().SetImagePos(image_pos)
# If mkimage is missing we'll have empty data,
# which will cause a FDT_ERR_BADMAGIC error
if self.mkimage in self.missing_bintools:
return
fdt = Fdt.FromData(self.GetData())
fdt.Scan()
for image_name, section in self._entries.items():
path = f"/images/{image_name}"
node = fdt.GetNode(path)
data_prop = node.props.get("data")
data_pos = fdt_util.GetInt(node, "data-position")
data_offset = fdt_util.GetInt(node, "data-offset")
data_size = fdt_util.GetInt(node, "data-size")
# Contents are inside the FIT
if data_prop is not None:
# GetOffset() returns offset of a fdt_property struct,
# which has 3 fdt32_t members before the actual data.
offset = data_prop.GetOffset() + 12
size = len(data_prop.bytes)
# External offset from the base of the FIT
elif data_pos is not None:
offset = data_pos
size = data_size
# External offset from the end of the FIT, not used in binman
elif data_offset is not None: # pragma: no cover
offset = fdt.GetFdtObj().totalsize() + data_offset
size = data_size
# This should never happen
else: # pragma: no cover
self.Raise(f'{path}: missing data properties')
section.SetOffsetSize(offset, size)
section.SetImagePos(self.image_pos)
def AddBintools(self, btools):
super().AddBintools(btools)
self.mkimage = self.AddBintool(btools, 'mkimage')
def CheckMissing(self, missing_list):
# We must use our private entry list for this since generator notes
# which are removed from self._entries will otherwise not show up as
# missing
for entry in self._priv_entries.values():
entry.CheckMissing(missing_list)