[PATCH 20/24] binman: Support splitting an ELF file into multiple nodes
Simon Glass
sjg at chromium.org
Wed Feb 23 23:59:06 CET 2022
Hi Alper,
On Tue, 15 Feb 2022 at 04:53, Alper Nebi Yasak <alpernebiyasak at gmail.com> wrote:
>
> On 08/02/2022 21:50, Simon Glass wrote:
> > Some boards need to load an ELF file using the 'loadables' property, but
> > the file has segments at different memory addresses. This means that it
> > cannot be supplied as a flat binary.
> >
> > Allow generating a separate node in the FIT for each segment in the ELF,
> > with a different load address for each.
> >
> > Signed-off-by: Simon Glass <sjg at chromium.org>
> > ---
> >
> > tools/binman/entries.rst | 146 +++++++++++
> > tools/binman/etype/fit.py | 259 ++++++++++++++++++-
> > tools/binman/ftest.py | 116 +++++++++
> > tools/binman/test/221_fit_split_elf.dts | 67 +++++
> > tools/binman/test/222_fit_bad_dir.dts | 9 +
> > tools/binman/test/223_fit_bad_dir_config.dts | 9 +
> > 6 files changed, 594 insertions(+), 12 deletions(-)
> > create mode 100644 tools/binman/test/221_fit_split_elf.dts
> > create mode 100644 tools/binman/test/222_fit_bad_dir.dts
> > create mode 100644 tools/binman/test/223_fit_bad_dir_config.dts
> >
> > diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
> > index d483169712..079fed1a9c 100644
> > --- a/tools/binman/entries.rst
> > +++ b/tools/binman/entries.rst
> > @@ -612,6 +612,9 @@ 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)
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >
> > @@ -655,6 +658,149 @@ 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
> > + segmnet
> > +
> > + 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
>
> I think all these should be done by default. I don't see the point of
> not setting the properties, or setting them manually to values that will
> be the same for multiple nodes.
My intent is to make things discoverable and obvious, so that magic
processing is explicit.
>
> > +
> > + fit,loadables
> > + Generates a `loadable = <...>` property with a list of the generated
> > + nodes (including all nodes if this operation is used multiple times)
>
> I think this could also be the default behaviour like the above, and
> even for configs which have a "loadables" property we could append to it
> by default.
>
> Either way, if the "loadables" property exists this needs to append to
> it instead of entirely replacing it. (Needs to append to "u-boot", see
> comment at example generated config below.)
Yes, makes sense.
>
> > +
> > +
> > +Here is an example showing ATF, TEE and a device tree all combined::
>
> This looks like a better place to write about the template node design
> so I'll try here instead of the previous patch. I've always thought they
> were ugly but I couldn't think of a good enough design when they were
> introduced. Maybe I have now.
>
> > +
> > + 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_SYS_TEXT_BASE>;
> > + u-boot-nodtb {
> > + };
> > + };
> > + @fdt-SEQ {
> > + description = "fdt-NAME.dtb";
> > + type = "flat_dt";
> > + compression = "none";
> > + };
>
> Instead of putting these into the images subnode, we could have
> images-level subnodes for the operations. For example, instead of the above:
>
> images at gen-fdt-nodes {
> fdt-list = "of-list";
>
> fdt {
> type = "flat_dt";
> compression = "none";
> };
> };
What does that mean, though? I presume it creates a second images {}
node, which is fine as dtc will merge them. But what about ordering?
I certainly prefer this in terms of elegance. I'm just not convinced
that people will understand it as well.
>
> We can remove the -SEQ if we always append a sequence number, and we can
> set "description" to "NAME.dtb" when it's missing, or do the replacement
> when it's given. We can go further and use "%s", "%(name)s" or "{name}"
> instead of "NAME" for python-ish formatting and likewise for the
> sequence number.
Yes, but again this adds more magic. For the Python formatting, we
still need to restrict what is put in there - e.g. we cannot just eval
an arbitrary varaible.
>
> > + @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 {
> > + };
> > + };
> > +
> > + @tee-SEQ {
> > + fit,operation = "split-elf";
> > + description = "TEE";
> > + type = "tee";
> > + arch = "arm64";
> > + os = "tee";
> > + compression = "none";
> > + fit,load;
> > + fit,entry;
> > + fit,data;
> > +
> > + op-tee {
> > + };
> > + };
> > + };
>
> Similarly, instead of the above two:
>
> images at split-elf {
> atf {
> description = "ARM Trusted Firmware";
> type = "firmware";
> arch = "arm64";
> os = "arm-trusted-firmware";
> compression = "none";
>
> atf-bl31 {
> };
> };
>
> tee {
> description = "TEE";
> type = "tee";
> arch = "arm64";
> os = "tee";
> compression = "none";
>
> op-tee {
> };
> };
> };
>
> The load, entry, data properties could be set automatically without
> explicit mention.
>
> > +
> > + configurations {
> > + default = "@config-DEFAULT-SEQ";
> > + @config-SEQ {
> > + description = "conf-NAME.dtb";
> > + fdt = "fdt-SEQ";
> > + firmware = "u-boot";
> > + fit,loadables;
> > + };
> > + };
>
> And instead of the above:
>
> configurations at gen-fdt-nodes {
> fdt-list = "of-list";
>
> config {
> fdt = "fdt";
> firmware = "u-boot";
> };
> };
>
> Again, we can automatically add a sequence number to "config" node names
> and "fdt" property value, and set/replace a description property.
>
> We can collect all loadables from the images at split-elf nodes and create
> or append to the "loadables" properties. Or we could specify "atf" and
> "fdt" in the loadables and they could be expanded with sequence numbers.
>
> And if the "default" property of "configurations" node (or even the
> whole node) is missing, we can make configurations at gen-fdt-nodes set it
> to the default-dt.
>
>
> (Remains to be seen if I'll be annoyed enough to implement this myself.)
We should talk about this some more though. I'm a bit worried it will
get complaints about too much magic. I am trying to make it obvious
that nodes get generated in certain places.
>
> > + };
> > +
> > +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";
> > + };
> > + atf-2 {
> > + data = <...contents of second segment...>;
> > + load = <0xff3b0000>;
> > + compression = "none";
> > + os = "arm-trusted-firmware";
> > + arch = "arm64";
> > + type = "firmware";
> > + description = "ARM Trusted Firmware";
> > + };
> > + };
> > +
> > +The same applies for OP-TEE if that is available.
> > +
> > +If each binary is not available, the relevant template node (@atf-SEQ or
> > + at tee-SEQ) is removed from the output.
>
> I'm running into the fake-file thing with op-tee and getting a "Magic
> number does not match" error on consecutive builds as the leftover fake
> op-tee isn't an ELF...
Yes, I added a TODO for that. I think the take files should go in a
binman-fake subdir, removed on startup.
>
> > +
> > +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 = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
> > + description = "rk3399-firefly.dtb";
> > + fdt = "fdt-1";
> > + firmware = "u-boot";
> > + };
> > + };
>
> These loadables/firmware values didn't work on my chromebook_kevin. The
> Rockchip generator gets me:
>
> firmware = "atf-1";
> loadables = "u-boot", "atf-2", "atf-3";
>
> instead of the ones above (I didn't pass an op-tee file), and these work
> when I hardcode them in the binman definition.
I believe this is fixed now.
[..]
> > - def _scan_node(subnode, depth, in_images):
> > + def _scan_split_elf(subnode, rel_path):
> > + #data, input_fname, uniq = self.collect_contents_to_file(
> > + entry = Entry.Create(self.section, subnode, etype='section')
> > + entry.ReadNode()
> > + self._fit_sections[rel_path] = entry
>
> I did rebase this on top of dm-pull-8feb22 and my other two patches, and
> this series started failing on CheckEntries() complaining about the
> template node's entry's content not fitting inside the FIT section. I
> didn't really track it down but I guess it's due to adding it to
> self._entries here or somewhere else.
Yes I ended up creating a separate list of entries. See what you think.
[..]
> > + def _add_split_elf(self, orig_node, node, data, missing):
> > + """Add nodes for the ELF file, one per group of contiguous segments
> > +
> > + The existing placeholder node is replaced
> > +
> > + Args:
> > + orig_node (Node): Template node from the binman definition
> > + node (Node): Node to replace (in the FIT being built)
> > + data (bytes): ELF-format data to process (may be empty)
> > + """
> > + # If any pieces are missing, skip this. The missing entries will show
> > + # an error
> > + if not missing:
> > + try:
> > + segments, entry = elf.read_segments(data)
> > + except ValueError as exc:
> > + self.Raise(f'Failed to read ELF file for {orig_node.path}: {str(exc)}')
> > + parent = node.parent
> > + for (seq, start, data) in segments:
> > + node_name = orig_node.name[1:].replace('SEQ', str(seq + 1))
> > + subnode = parent.AddSubnode(node_name) #, before=node)
> > + self._loadables.append(subnode)
> > + for pname, prop in orig_node.props.items():
> > + if not pname.startswith('fit,'):
> > + subnode.AddData(pname, prop.bytes)
> > + elif pname == 'fit,load':
> > + subnode.AddInt('load', start)
> > + elif pname == 'fit,entry':
> > + if not seq:
> > + subnode.AddInt('entry', entry)
> > + elif pname == 'fit,data':
> > + subnode.AddData('data', data)
> > + elif pname != 'fit,operation':
> > + self.Raise(
> > + f"Unknown directive in '{subnode.path}': '{pname}'")
> > +
> > + # Delete the template node as it has served its purpose
> > + node.Delete()
>
> Also, with the fit-as-section patches I get an error in SetImagePos()
> due to a template subentry in self._entries getting None as its node,
> thought it's related to the deletion here but didn't confirm.
Yes it cannot be deleted twice. Fixed in latest series.
>
> > +
> > def _BuildInput(self, fdt):
> > """Finish the FIT by adding the 'data' properties to it
> >
> > @@ -393,13 +605,36 @@ class Entry_fit(Entry):
> > New fdt contents (bytes)
> > """
> > for path, section in self._fit_sections.items():
> > - node = fdt.GetNode(path)
> > # Entry_section.ObtainContents() either returns True or
> > # raises an exception.
> > section.ObtainContents()
> > section.Pack(0)
> > data = section.GetData()
> > - node.AddData('data', data)
> > + missing_list = []
> > + section.CheckMissing(missing_list)
> > +
> > + node = fdt.GetNode(path)
> > + oper = self._get_operation(section._node)
> > + if oper == OP_GEN_FDT_NODES:
> > + node.AddData('data', data)
> > + elif oper == OP_SPLIT_ELF:
> > + self._add_split_elf(section._node, node, data,
> > + bool(missing_list))
> > +
> > + # Set up the 'firmware' and 'loadables' properties in all
> > + # 'configurations' nodes, but only if we are generating FDTs. Note that
> > + # self._config_node is set in _scan_gen_fdt_nodes()
> > + node = fdt.GetNode('/configurations')
> > + if self._config_node:
> > + for subnode in node.subnodes:
> > + for pname, prop in self._config_node.props.items():
> > + if pname == 'fit,loadables':
> > + subnode.AddStringList(
> > + 'loadables',
> > + [node.name for node in self._loadables])
>
> As mentioned above, if "loadables" exists this should append to it.
I agree with that, but have not done it yet.
[..]
> > + def testFitSplitElfBadElf(self):
> > + """Test a FIT split-elf operation with an invalid ELF file"""
> > + TestFunctional._MakeInputFile('bad.elf', tools.GetBytes(100, 100))
> > + entry_args = {
> > + 'of-list': 'test-fdt1 test-fdt2',
> > + 'default-dt': 'test-fdt2',
> > + 'atf-bl31-path': 'bad.elf',
>
> This test fails for me with the fit-as-section patches but adding
> 'op-tee-path' here like the ones above makes it succeed. Though, you
> might have meant to test a missing op-tee here in addition to the bad.elf.
>
> Could use an independent test that checks a missing op-tee doesn't
> result in an error, as it should just omit the nodes in the FIT.
Should be fixed with new series.
[..]
Thanks for all the comments and ideas. I think this all need a little
more thought...
Regards,
Simon
More information about the U-Boot
mailing list