[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