diff mbox series

[v2,20/25] binman: Support splitting an ELF file into multiple nodes

Message ID 20220223230040.159317-21-sjg@chromium.org
State Superseded
Delegated to: Simon Glass
Headers show
Series binman: rockchip: Migrate from rockchip SPL_FIT_GENERATOR script | expand

Commit Message

Simon Glass Feb. 23, 2022, 11 p.m. UTC
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.

Also add checks that the fit,xxx directives are valid.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v2:
- Rewrite this to use the new FIT entry-type implementation
- Rename op-tee to tee-os

 tools/binman/entries.rst                     | 146 ++++++++++++
 tools/binman/etype/fit.py                    | 230 ++++++++++++++++++-
 tools/binman/ftest.py                        | 147 ++++++++++++
 tools/binman/test/226_fit_split_elf.dts      |  67 ++++++
 tools/binman/test/227_fit_bad_dir.dts        |   9 +
 tools/binman/test/228_fit_bad_dir_config.dts |   9 +
 6 files changed, 598 insertions(+), 10 deletions(-)
 create mode 100644 tools/binman/test/226_fit_split_elf.dts
 create mode 100644 tools/binman/test/227_fit_bad_dir.dts
 create mode 100644 tools/binman/test/228_fit_bad_dir_config.dts

Comments

Alper Nebi Yasak March 3, 2022, 9:10 p.m. UTC | #1
On 24/02/2022 02:00, 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.
> 
> Also add checks that the fit,xxx directives are valid.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
> Changes in v2:
> - Rewrite this to use the new FIT entry-type implementation
> - Rename op-tee to tee-os
> 
>  tools/binman/entries.rst                     | 146 ++++++++++++
>  tools/binman/etype/fit.py                    | 230 ++++++++++++++++++-
>  tools/binman/ftest.py                        | 147 ++++++++++++
>  tools/binman/test/226_fit_split_elf.dts      |  67 ++++++
>  tools/binman/test/227_fit_bad_dir.dts        |   9 +
>  tools/binman/test/228_fit_bad_dir_config.dts |   9 +
>  6 files changed, 598 insertions(+), 10 deletions(-)
>  create mode 100644 tools/binman/test/226_fit_split_elf.dts
>  create mode 100644 tools/binman/test/227_fit_bad_dir.dts
>  create mode 100644 tools/binman/test/228_fit_bad_dir_config.dts
> 
> [...]
> 
> diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
> index 30b20a07a2..63d552ed19 100644> > [...]
> 
> @@ -154,6 +159,149 @@ class Entry_fit(Entry_section):
>  
>      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

segmnet -> segment, missed it the first time around.

> +
> +        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,loadables
> +            Generates a `loadable = <...>` property with a list of the generated
> +            nodes (including all nodes if this operation is used multiple times)
> 
> [...]
>  
> @@ -363,11 +514,20 @@ class Entry_fit(Entry_section):
>                      fname = tools.get_input_filename(fdt_fname + '.dtb')
>                      with fsw.add_node(node_name):
>                          for pname, prop in node.props.items():
> -                            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)
> +                            if pname == 'fit,loadables':
> +                                val = '\0'.join(self._loadables) + '\0'
> +                                fsw.property('loadables', val.encode('utf-8'))
> +                            elif pname == 'fit,operation':
> +                                pass
> +                            elif pname.startswith('fit,'):
> +                                self._raise(base_node, 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)

I get two distinct "loadables" properties with this config fragment,
and fdtget and mkimage -l only considers the first one:

  @config-SEQ {
     description = "NAME.dtb";
     fdt = "fdt-SEQ";
     firmware = "atf-1";
     loadables = "u-boot";
     fit,loadables;
  };

  $ fdtget simple-bin.fit.fit /configurations/config-1 -p
  description
  fdt
  firmware
  loadables
  loadables

  $ fdtget simple-bin.fit.fit /configurations/config-1 loadables
  u-boot

  $ mkimage -l simple-bin.fit.fit
  FIT description: FIT image for U-Boot with bl31 (TF-A)
  Created:         Thu Feb 24 17:36:36 2022
   Image 0 (u-boot)
    Description:  U-Boot (64-bit)
    Created:      Thu Feb 24 17:36:36 2022
    Type:         Standalone Program
    Compression:  uncompressed
    Data Size:    727192 Bytes = 710.15 KiB = 0.69 MiB
    Architecture: AArch64
    Load Address: 0x00200000
    Entry Point:  unavailable
   Image 1 (atf-1)
    Description:  ARM Trusted Firmware
    Created:      Thu Feb 24 17:36:36 2022
    Type:         Firmware
    Compression:  uncompressed
    Data Size:    139342 Bytes = 136.08 KiB = 0.13 MiB
    Architecture: AArch64
    OS:           ARM Trusted Firmware
    Load Address: 0x00040000
   Image 2 (atf-2)
    Description:  ARM Trusted Firmware
    Created:      Thu Feb 24 17:36:36 2022
    Type:         Firmware
    Compression:  uncompressed
    Data Size:    8024 Bytes = 7.84 KiB = 0.01 MiB
    Architecture: AArch64
    OS:           ARM Trusted Firmware
    Load Address: 0xff3b0000
   Image 3 (atf-3)
    Description:  ARM Trusted Firmware
    Created:      Thu Feb 24 17:36:36 2022
    Type:         Firmware
    Compression:  uncompressed
    Data Size:    8192 Bytes = 8.00 KiB = 0.01 MiB
    Architecture: AArch64
    OS:           ARM Trusted Firmware
    Load Address: 0xff8c0000
   Image 4 (fdt-1)
    Description:  fdt-rk3399-gru-bob
    Created:      Thu Feb 24 17:36:36 2022
    Type:         Flat Device Tree
    Compression:  uncompressed
    Data Size:    69392 Bytes = 67.77 KiB = 0.07 MiB
    Architecture: Unknown Architecture
   Default Configuration: 'config-1'
   Configuration 0 (config-1)
    Description:  rk3399-gru-bob.dtb
    Kernel:       unavailable
    Firmware:     atf-1
    FDT:          fdt-1
    Loadables:    u-boot

I didn't test if my chromebook_kevin boots in this double-loadables
configuration, though.

>  
>                          # Add data for 'images' nodes (but not 'config')
>                          if depth == 1 and in_images:
> @@ -380,7 +540,43 @@ class Entry_fit(Entry_section):
>                      else:
>                          self.Raise("Generator node requires 'fit,fdt-list' property")
>  
> -        def _gen_node(base_node, node, depth, in_images):
> +        def _gen_split_elf(base_node, node, elf_data, missing):
> +            """Add nodes for the ELF file, one per group of contiguous segments
> +
> +            Args:
> +                node (Node): Template node from the binman definition

node -> base_node

> +                node (Node): Node to replace (in the FIT being built)
> +                data (bytes): ELF-format data to process (may be empty)
> +                missing (bool): True if any of the data is missing
> +
> +            """
> +            # If any pieces are missing, skip this. The missing entries will
> +            # show an error
> +            if not missing:
> +                try:
> +                    segments, entry = elf.read_loadable_segments(elf_data)
> +                except ValueError as exc:
> +                    self._raise(base_node, node,
> +                                f'Failed to read ELF file: {str(exc)}')
> +                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 not seq:

I'd prefer seq == 0 as it's a number.

> +                                    fsw.property_u32('entry', entry)
> +                            elif pname == 'fit,data':
> +                                fsw.property('data', bytes(data))
> +                            elif pname != 'fit,operation':
> +                                self._raise(base_node, node,
> +                                            f"Unknown directive '{pname}'")
> +
> +        def _gen_node(base_node, node, depth, in_images, entry):
>              """Generate nodes from a template
>  
>              This creates one node for each member of self._fdts using the
> [...]
Simon Glass March 6, 2022, 3:08 a.m. UTC | #2
Hi Alper,

On Thu, 3 Mar 2022 at 14:17, Alper Nebi Yasak <alpernebiyasak@gmail.com> wrote:
>
> On 24/02/2022 02:00, 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.
> >
> > Also add checks that the fit,xxx directives are valid.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > Changes in v2:
> > - Rewrite this to use the new FIT entry-type implementation
> > - Rename op-tee to tee-os
> >
> >  tools/binman/entries.rst                     | 146 ++++++++++++
> >  tools/binman/etype/fit.py                    | 230 ++++++++++++++++++-
> >  tools/binman/ftest.py                        | 147 ++++++++++++
> >  tools/binman/test/226_fit_split_elf.dts      |  67 ++++++
> >  tools/binman/test/227_fit_bad_dir.dts        |   9 +
> >  tools/binman/test/228_fit_bad_dir_config.dts |   9 +
> >  6 files changed, 598 insertions(+), 10 deletions(-)
> >  create mode 100644 tools/binman/test/226_fit_split_elf.dts
> >  create mode 100644 tools/binman/test/227_fit_bad_dir.dts
> >  create mode 100644 tools/binman/test/228_fit_bad_dir_config.dts
> >
> > [...]
> >
> > diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
> > index 30b20a07a2..63d552ed19 100644> > [...]
> >
> > @@ -154,6 +159,149 @@ class Entry_fit(Entry_section):
> >
> >      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
>
> segmnet -> segment, missed it the first time around.
>
> > +
> > +        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,loadables
> > +            Generates a `loadable = <...>` property with a list of the generated
> > +            nodes (including all nodes if this operation is used multiple times)
> >
> > [...]
> >
> > @@ -363,11 +514,20 @@ class Entry_fit(Entry_section):
> >                      fname = tools.get_input_filename(fdt_fname + '.dtb')
> >                      with fsw.add_node(node_name):
> >                          for pname, prop in node.props.items():
> > -                            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)
> > +                            if pname == 'fit,loadables':
> > +                                val = '\0'.join(self._loadables) + '\0'
> > +                                fsw.property('loadables', val.encode('utf-8'))
> > +                            elif pname == 'fit,operation':
> > +                                pass
> > +                            elif pname.startswith('fit,'):
> > +                                self._raise(base_node, 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)
>
> I get two distinct "loadables" properties with this config fragment,
> and fdtget and mkimage -l only considers the first one:
>
>   @config-SEQ {
>      description = "NAME.dtb";
>      fdt = "fdt-SEQ";
>      firmware = "atf-1";
>      loadables = "u-boot";
>      fit,loadables;
>   };

Yes that needs fixing, but I'll leave that case for now. It needs its own test.

>
>   $ fdtget simple-bin.fit.fit /configurations/config-1 -p
>   description
>   fdt
>   firmware
>   loadables
>   loadables
>
>   $ fdtget simple-bin.fit.fit /configurations/config-1 loadables
>   u-boot
>
>   $ mkimage -l simple-bin.fit.fit
>   FIT description: FIT image for U-Boot with bl31 (TF-A)
>   Created:         Thu Feb 24 17:36:36 2022
>    Image 0 (u-boot)
>     Description:  U-Boot (64-bit)
>     Created:      Thu Feb 24 17:36:36 2022
>     Type:         Standalone Program
>     Compression:  uncompressed
>     Data Size:    727192 Bytes = 710.15 KiB = 0.69 MiB
>     Architecture: AArch64
>     Load Address: 0x00200000
>     Entry Point:  unavailable
>    Image 1 (atf-1)
>     Description:  ARM Trusted Firmware
>     Created:      Thu Feb 24 17:36:36 2022
>     Type:         Firmware
>     Compression:  uncompressed
>     Data Size:    139342 Bytes = 136.08 KiB = 0.13 MiB
>     Architecture: AArch64
>     OS:           ARM Trusted Firmware
>     Load Address: 0x00040000
>    Image 2 (atf-2)
>     Description:  ARM Trusted Firmware
>     Created:      Thu Feb 24 17:36:36 2022
>     Type:         Firmware
>     Compression:  uncompressed
>     Data Size:    8024 Bytes = 7.84 KiB = 0.01 MiB
>     Architecture: AArch64
>     OS:           ARM Trusted Firmware
>     Load Address: 0xff3b0000
>    Image 3 (atf-3)
>     Description:  ARM Trusted Firmware
>     Created:      Thu Feb 24 17:36:36 2022
>     Type:         Firmware
>     Compression:  uncompressed
>     Data Size:    8192 Bytes = 8.00 KiB = 0.01 MiB
>     Architecture: AArch64
>     OS:           ARM Trusted Firmware
>     Load Address: 0xff8c0000
>    Image 4 (fdt-1)
>     Description:  fdt-rk3399-gru-bob
>     Created:      Thu Feb 24 17:36:36 2022
>     Type:         Flat Device Tree
>     Compression:  uncompressed
>     Data Size:    69392 Bytes = 67.77 KiB = 0.07 MiB
>     Architecture: Unknown Architecture
>    Default Configuration: 'config-1'
>    Configuration 0 (config-1)
>     Description:  rk3399-gru-bob.dtb
>     Kernel:       unavailable
>     Firmware:     atf-1
>     FDT:          fdt-1
>     Loadables:    u-boot
>
> I didn't test if my chromebook_kevin boots in this double-loadables
> configuration, though.

I wish we could get the kevin patches applied. What on earth is going
on there? I haven't been able to test any of this on kevin.



[..]

Regards,
Simon
diff mbox series

Patch

diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index 484cde5c80..a915f64d46 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
+
+    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_SYS_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 {
+                };
+            };
+
+            @tee-SEQ {
+                fit,operation = "split-elf";
+                description = "TEE";
+                type = "tee";
+                arch = "arm64";
+                os = "tee";
+                compression = "none";
+                fit,load;
+                fit,entry;
+                fit,data;
+
+                tee-os {
+                };
+            };
+        };
+
+        configurations {
+            default = "@config-DEFAULT-SEQ";
+            @config-SEQ {
+                description = "conf-NAME.dtb";
+                fdt = "fdt-SEQ";
+                firmware = "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";
+        };
+        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
+@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 = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
+            description = "rk3399-firefly.dtb";
+            fdt = "fdt-1";
+            firmware = "u-boot";
+        };
+    };
+
+U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
+(ATF and TEE), then proceed with the boot.
+
 
 
 Entry: fmap: An entry which contains an Fmap section
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 30b20a07a2..63d552ed19 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -9,14 +9,16 @@  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 = range(1)
+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):
@@ -112,6 +114,9 @@  class Entry_fit(Entry_section):
         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)
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -154,6 +159,149 @@  class Entry_fit(Entry_section):
 
     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
+
+        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_SYS_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 {
+                    };
+                };
+
+                @tee-SEQ {
+                    fit,operation = "split-elf";
+                    description = "TEE";
+                    type = "tee";
+                    arch = "arm64";
+                    os = "tee";
+                    compression = "none";
+                    fit,load;
+                    fit,entry;
+                    fit,data;
+
+                    tee-os {
+                    };
+                };
+            };
+
+            configurations {
+                default = "@config-DEFAULT-SEQ";
+                @config-SEQ {
+                    description = "conf-NAME.dtb";
+                    fdt = "fdt-SEQ";
+                    firmware = "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";
+            };
+            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
+    @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 = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
+                description = "rk3399-firefly.dtb";
+                fdt = "fdt-1";
+                firmware = "u-boot";
+            };
+        };
+
+    U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
+    (ATF and TEE), then proceed with the boot.
     """
     def __init__(self, section, etype, node):
         """
@@ -167,6 +315,7 @@  class Entry_fit(Entry_section):
                 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
@@ -188,6 +337,8 @@  class Entry_fit(Entry_section):
                                                                   str)])[0]
         self.mkimage = None
 
+        self._loadables = []
+
     def ReadNode(self):
         super().ReadNode()
 
@@ -341,7 +492,7 @@  class Entry_fit(Entry_section):
                 return
             fsw.property(pname, prop.bytes)
 
-        def _gen_fdt_nodes(node, depth, in_images):
+        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
@@ -363,11 +514,20 @@  class Entry_fit(Entry_section):
                     fname = tools.get_input_filename(fdt_fname + '.dtb')
                     with fsw.add_node(node_name):
                         for pname, prop in node.props.items():
-                            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)
+                            if pname == 'fit,loadables':
+                                val = '\0'.join(self._loadables) + '\0'
+                                fsw.property('loadables', val.encode('utf-8'))
+                            elif pname == 'fit,operation':
+                                pass
+                            elif pname.startswith('fit,'):
+                                self._raise(base_node, 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:
@@ -380,7 +540,43 @@  class Entry_fit(Entry_section):
                     else:
                         self.Raise("Generator node requires 'fit,fdt-list' property")
 
-        def _gen_node(base_node, node, depth, in_images):
+        def _gen_split_elf(base_node, node, elf_data, missing):
+            """Add nodes for the ELF file, one per group of contiguous segments
+
+            Args:
+                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)
+                missing (bool): True if any of the data is missing
+
+            """
+            # If any pieces are missing, skip this. The missing entries will
+            # show an error
+            if not missing:
+                try:
+                    segments, entry = elf.read_loadable_segments(elf_data)
+                except ValueError as exc:
+                    self._raise(base_node, node,
+                                f'Failed to read ELF file: {str(exc)}')
+                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 not seq:
+                                    fsw.property_u32('entry', entry)
+                            elif pname == 'fit,data':
+                                fsw.property('data', bytes(data))
+                            elif pname != 'fit,operation':
+                                self._raise(base_node, node,
+                                            f"Unknown directive '{pname}'")
+
+        def _gen_node(base_node, node, depth, in_images, entry):
             """Generate nodes from a template
 
             This creates one node for each member of self._fdts using the
@@ -399,7 +595,18 @@  class Entry_fit(Entry_section):
             """
             oper = self._get_operation(base_node, node)
             if oper == OP_GEN_FDT_NODES:
-                _gen_fdt_nodes(node, depth, in_images)
+                _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_list = []
+                entry.ObtainContents()
+                entry.Pack(0)
+                data = entry.GetData()
+                entry.CheckMissing(missing_list)
+
+                _gen_split_elf(base_node, node, data, bool(missing_list))
 
         def _add_node(base_node, depth, node):
             """Add nodes to the output FIT
@@ -437,7 +644,8 @@  class Entry_fit(Entry_section):
                     # fsw.add_node() or _add_node() for it.
                     pass
                 elif self.GetImage().generate and subnode.name.startswith('@'):
-                    _gen_node(base_node, subnode, depth, in_images)
+                    entry = self._priv_entries.get(subnode_path)
+                    _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
@@ -451,8 +659,10 @@  class Entry_fit(Entry_section):
         fsw = libfdt.FdtSw()
         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
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 16463518aa..ec8e3d4d91 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -202,6 +202,13 @@  class TestFunctional(unittest.TestCase):
 
         TestFunctional._MakeInputFile('env.txt', ENV_DATA)
 
+        # ELF file with two sections in different parts of memory, used for both
+        # ATF and OP_TEE
+        TestFunctional._MakeInputFile('bl31.elf',
+            tools.read_file(cls.ElfTestFile('elf_sections')))
+        TestFunctional._MakeInputFile('tee.elf',
+            tools.read_file(cls.ElfTestFile('elf_sections')))
+
         cls.have_lz4 = comp_util.HAVE_LZ4
 
     @classmethod
@@ -5340,6 +5347,146 @@  fdt         fdtmap                Extract the devicetree blob from the fdtmap
             err,
             "Image '.*' has faked external blobs and is non-functional: .*")
 
+    def testFitSplitElf(self):
+        """Test an image with an FIT with an split-elf operation"""
+        entry_args = {
+            'of-list': 'test-fdt1 test-fdt2',
+            'default-dt': 'test-fdt2',
+            'atf-bl31-path': 'bl31.elf',
+            'tee-os-path': 'tee.elf',
+        }
+        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+        data = self._DoReadFileDtb(
+            '226_fit_split_elf.dts',
+            entry_args=entry_args,
+            extra_indirs=[test_subdir])[0]
+
+        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
+        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
+
+        base_keys = {'description', 'type', 'arch', 'os', 'compression',
+                     'data', 'load'}
+        dtb = fdt.Fdt.FromData(fit_data)
+        dtb.Scan()
+
+        elf_data = tools.read_file(os.path.join(self._indir, 'bl31.elf'))
+        segments, entry = elf.read_loadable_segments(elf_data)
+
+        # We assume there are two segments
+        self.assertEquals(2, len(segments))
+
+        atf1 = dtb.GetNode('/images/atf-1')
+        _, start, data = segments[0]
+        self.assertEqual(base_keys | {'entry'}, atf1.props.keys())
+        self.assertEqual(entry,
+                         fdt_util.fdt32_to_cpu(atf1.props['entry'].value))
+        self.assertEqual(start,
+                         fdt_util.fdt32_to_cpu(atf1.props['load'].value))
+        self.assertEqual(data, atf1.props['data'].bytes)
+
+        atf2 = dtb.GetNode('/images/atf-2')
+        self.assertEqual(base_keys, atf2.props.keys())
+        _, start, data = segments[1]
+        self.assertEqual(start,
+                         fdt_util.fdt32_to_cpu(atf2.props['load'].value))
+        self.assertEqual(data, atf2.props['data'].bytes)
+
+        conf = dtb.GetNode('/configurations')
+        self.assertEqual({'default'}, conf.props.keys())
+
+        for subnode in conf.subnodes:
+            self.assertEqual({'description', 'fdt', 'loadables'},
+                             subnode.props.keys())
+            self.assertEqual(
+                ['atf-1', 'atf-2', 'tee-1', 'tee-2'],
+                fdt_util.GetStringList(subnode, 'loadables'))
+
+    def _check_bad_fit(self, dts):
+        """Check a bad FIT
+
+        This runs with the given dts and returns the assertion raised
+
+        Args:
+            dts (str): dts filename to use
+
+        Returns:
+            str: Assertion string raised
+        """
+        entry_args = {
+            'of-list': 'test-fdt1 test-fdt2',
+            'default-dt': 'test-fdt2',
+            'atf-bl31-path': 'bl31.elf',
+            'tee-os-path': 'tee.elf',
+        }
+        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFileDtb(dts, entry_args=entry_args,
+                                extra_indirs=[test_subdir])[0]
+        return str(exc.exception)
+
+    def testFitSplitElfBadElf(self):
+        """Test a FIT split-elf operation with an invalid ELF file"""
+        TestFunctional._MakeInputFile('bad.elf', tools.get_bytes(100, 100))
+        entry_args = {
+            'of-list': 'test-fdt1 test-fdt2',
+            'default-dt': 'test-fdt2',
+            'atf-bl31-path': 'bad.elf',
+            'tee-os-path': 'tee.elf',
+        }
+        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFileDtb(
+                '226_fit_split_elf.dts',
+                entry_args=entry_args,
+                extra_indirs=[test_subdir])[0]
+        self.assertIn(
+            "Node '/binman/fit': subnode 'images/@atf-SEQ': Failed to read ELF file: Magic number does not match",
+            str(exc.exception))
+
+    def testFitSplitElfBadDirective(self):
+        """Test a FIT split-elf invalid fit,xxx directive in an image node"""
+        err = self._check_bad_fit('227_fit_bad_dir.dts')
+        self.assertIn(
+            "Node '/binman/fit': subnode 'images/@atf-SEQ': Unknown directive 'fit,something'",
+            err)
+
+    def testFitSplitElfBadDirectiveConfig(self):
+        """Test a FIT split-elf with invalid fit,xxx directive in config"""
+        err = self._check_bad_fit('228_fit_bad_dir_config.dts')
+        self.assertEqual(
+            "Node '/binman/fit': subnode 'configurations/@config-SEQ': Unknown directive 'fit,config'",
+            err)
+
+    def checkFitSplitElf(self, **kwargs):
+        """Test an split-elf FIT with a missing ELF file"""
+        entry_args = {
+            'of-list': 'test-fdt1 test-fdt2',
+            'default-dt': 'test-fdt2',
+            'atf-bl31-path': 'bl31.elf',
+            'tee-os-path': 'missing.elf',
+        }
+        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+        with test_util.capture_sys_output() as (stdout, stderr):
+            self._DoTestFile(
+                '226_fit_split_elf.dts', entry_args=entry_args,
+                extra_indirs=[test_subdir], **kwargs)
+        err = stderr.getvalue()
+        return err
+
+    def testFitSplitElfMissing(self):
+        """Test an split-elf FIT with a missing ELF file"""
+        err = self.checkFitSplitElf(allow_missing=True)
+        self.assertRegex(
+            err,
+            "Image '.*' is missing external blobs and is non-functional: .*")
+
+    def testFitSplitElfFaked(self):
+        """Test an split-elf FIT with faked ELF file"""
+        err = self.checkFitSplitElf(allow_missing=True, allow_fake_blobs=True)
+        self.assertRegex(
+            err,
+            "Image '.*' is missing external blobs and is non-functional: .*")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/test/226_fit_split_elf.dts b/tools/binman/test/226_fit_split_elf.dts
new file mode 100644
index 0000000000..fab15338b2
--- /dev/null
+++ b/tools/binman/test/226_fit_split_elf.dts
@@ -0,0 +1,67 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		u-boot {
+		};
+		fit {
+			description = "test-desc";
+			#address-cells = <1>;
+			fit,fdt-list = "of-list";
+
+			images {
+				@fdt-SEQ {
+					description = "fdt-NAME.dtb";
+					type = "flat_dt";
+					compression = "none";
+				};
+				atf: @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;
+
+					tee-os {
+					};
+				};
+			};
+
+			configurations {
+				default = "@config-DEFAULT-SEQ";
+				config: @config-SEQ {
+					description = "conf-NAME.dtb";
+					fdt = "fdt-SEQ";
+					fit,loadables;
+				};
+			};
+		};
+
+		u-boot-nodtb {
+		};
+	};
+};
diff --git a/tools/binman/test/227_fit_bad_dir.dts b/tools/binman/test/227_fit_bad_dir.dts
new file mode 100644
index 0000000000..51f4816c4c
--- /dev/null
+++ b/tools/binman/test/227_fit_bad_dir.dts
@@ -0,0 +1,9 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#include "226_fit_split_elf.dts"
+
+&atf {
+	fit,something = "bad";
+};
diff --git a/tools/binman/test/228_fit_bad_dir_config.dts b/tools/binman/test/228_fit_bad_dir_config.dts
new file mode 100644
index 0000000000..825a346c3e
--- /dev/null
+++ b/tools/binman/test/228_fit_bad_dir_config.dts
@@ -0,0 +1,9 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#include "226_fit_split_elf.dts"
+
+&config {
+	fit,config = "bad";
+};