diff mbox series

[v3,21/26] binman: Support splitting an ELF file into multiple nodes

Message ID 20220306031917.3005215-22-sjg@chromium.org
State Accepted
Commit 40c8bdd87e3baa44314720d0d51d90c41e633ca3
Delegated to: Simon Glass
Headers show
Series binman: rockchip: Migrate from rockchip SPL_FIT_GENERATOR script | expand

Commit Message

Simon Glass March 6, 2022, 3:19 a.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 v3:
- Fix 'segmnet' typo
- Use seq == 0 instead of 'not seq'

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                    | 229 ++++++++++++++++++-
 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, 597 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 10, 2022, 7:30 p.m. UTC | #1
On 06/03/2022 06:19, 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 v3:
> - Fix 'segmnet' typo
> - Use seq == 0 instead of 'not seq'
> 
> 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                    | 229 ++++++++++++++++++-
>  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, 597 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

I still can't like this enough to add a Reviewed-by, but I guess you'll
apply the series up to and maybe including this, so:

Acked-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
Simon Glass March 12, 2022, 5:59 p.m. UTC | #2
Hi Alper,

On Thu, 10 Mar 2022 at 12:36, Alper Nebi Yasak <alpernebiyasak@gmail.com> wrote:
>
> On 06/03/2022 06:19, 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 v3:
> > - Fix 'segmnet' typo
> > - Use seq == 0 instead of 'not seq'
> >
> > 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                    | 229 ++++++++++++++++++-
> >  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, 597 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
>
> I still can't like this enough to add a Reviewed-by, but I guess you'll
> apply the series up to and maybe including this, so:
>
> Acked-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>

Yes OK I will do that in the next few days.

Then I will await your thoughts on next steps.

Regards,
Simon
diff mbox series

Patch

diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index 484cde5c80..be8de5560c 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
+        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,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 49f89851b0..85d8cfcc2b 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
+            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,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
@@ -174,6 +323,7 @@  class Entry_fit(Entry_section):
         self._fdts = None
         self.mkimage = None
         self._priv_entries = {}
+        self._loadables = []
 
     def ReadNode(self):
         super().ReadNode()
@@ -337,7 +487,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
@@ -359,11 +509,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_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:
@@ -376,7 +535,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:
+                base_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_subnode(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 seq == 0:
+                                    fsw.property_u32('entry', entry)
+                            elif pname == 'fit,data':
+                                fsw.property('data', bytes(data))
+                            elif pname != 'fit,operation':
+                                self._raise_subnode(
+                                    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
@@ -395,7 +590,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
@@ -433,7 +639,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
@@ -447,8 +654,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 690fa96d07..7ce9f9a7d8 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
@@ -5323,6 +5330,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";
+};