Hi Francesco

This patch looks very nice. Also having FIT support in the SPL only
sounds like an interesting idea which could potentially simplify some
things.

As a long term goal we should evaluate if this approach could become
the new standard for adding artifacts to a FIT image. Not sure what
that would mean in terms of testing and signing the artifacts. But
definitely interesting to be considered for the future.

Regards,
Adrian

On Sat, 2026-02-28 at 00:37 +0100, Francesco Valla via
lists.openembedded.org wrote:
> Allow a user to insert additional, arbitrary loadables in a FIT
> image.
> The loadables can be specified through the FIT_LOADABLES variable as
> a list, with parameters defined by flags on dedicated FIT_LOADABLE_*
> variables; they will be included in all configurations.
> 
> Sensible defaults will be used for some parameters (type,
> compression,
> description, arch, os) if the corresponding flag is not set, while
> others (load address and entry point) will be omitted in the final
> FIT
> image.
> 
> As an example, the following configuration can be specified to add as
> loadables a TF-A BL31 firmware and a (compressed) TEE firmware, to be
> loaded respectively at 0x204E0000 and 0x96000000:
> 
>   FIT_LOADABLES = "atf tee"
> 
>   FIT_LOADABLE_FILENAME[atf] = "bl31.bin"
>   FIT_LOADABLE_TYPE[atf] = "tfa-bl31"
>   FIT_LOADABLE_ARCH[atf] = "arm64"
>   FIT_LOADABLE_OS[atf] = "arm-trusted-firmware"
>   FIT_LOADABLE_LOADADDRESS[atf] = "0x204E0000"
> 
>   FIT_LOADABLE_FILENAME[tee] = "tee.bin.gz"
>   FIT_LOADABLE_COMPRESSSION[tee] = "gzip"
>   FIT_LOADABLE_TYPE[tee] = "tee"
>   FIT_LOADABLE_OS[tee] = "tee"
>   FIT_LOADABLE_LOADADDRESS[tee] = "0x21000000"
> 
> Signed-off-by: Francesco Valla <[email protected]>
> ---
>  meta/classes-recipe/kernel-fit-image.bbclass | 33
> +++++++++++++++++++
>  meta/conf/image-fitimage.conf                | 18 +++++++++++
>  meta/lib/oe/fitimage.py                      | 30 ++++++++++++++++++
>  meta/lib/oeqa/selftest/cases/fitimage.py     | 47
> ++++++++++++++++++++++++++++
>  4 files changed, 128 insertions(+)
> 
> diff --git a/meta/classes-recipe/kernel-fit-image.bbclass
> b/meta/classes-recipe/kernel-fit-image.bbclass
> index
> 4880027b210196d25f83d7e683c37bd5e66575d3..48ccdcf4a9d97a1854446521fd4
> ed8a020153e87 100644
> --- a/meta/classes-recipe/kernel-fit-image.bbclass
> +++ b/meta/classes-recipe/kernel-fit-image.bbclass
> @@ -139,6 +139,39 @@ python do_compile() {
>          if not found:
>              bb.fatal("Could not find a valid initramfs type for %s,
> the supported types are: %s" % (d.getVar('INITRAMFS_IMAGE_NAME'),
> d.getVar('FIT_SUPPORTED_INITRAMFS_FSTYPES')))
>  
> +    #
> +    # Prepare loadables sections
> +    #
> +    for loadable in d.getVar('FIT_LOADABLES').split():
> +        loadable_file = d.getVarFlag('FIT_LOADABLE_FILENAME',
> loadable)
> +        if not loadable_file:
> +            bb.fatal("File for loadable %s not specified through
> FIT_LOADABLE_FILENAME[%s]" % (loadable, loadable))
> +
> +        loadable_loadaddress =
> d.getVarFlag('FIT_LOADABLE_LOADADDRESS', loadable)
> +        if not loadable_loadaddress:
> +            bb.fatal("Load address for loadable %s not specified
> through FIT_LOADABLE_LOADADDRESS[%s]" % (loadable, loadable))
> +
> +        # Optional parameters
> +        loadable_description =
> d.getVarFlag('FIT_LOADABLE_DESCRIPTION', loadable) or ("%s loadable"
> % loadable)
> +        loadable_compression =
> d.getVarFlag('FIT_LOADABLE_COMPRESSION', loadable)
> +        loadable_type = d.getVarFlag('FIT_LOADABLE_TYPE', loadable)
> +        loadable_arch = d.getVarFlag('FIT_LOADABLE_ARCH', loadable)
> +        loadable_os = d.getVarFlag('FIT_LOADABLE_OS', loadable)
> +        loadable_entrypoint =
> d.getVarFlag('FIT_LOADABLE_ENTRYPOINT', loadable)
> +
> +        # Check if loadable artifact exists
> +        loadable_path = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"),
> loadable_file)
> +        if not os.path.exists(loadable_path):
> +            bb.fatal("File for loadable %s not found at %s" %
> (loadable, loadable_path))
> +
> +        root_node.fitimage_emit_section_loadable(loadable,
> +                                                 loadable_path,
> loadable_type,
> +                                                
> loadable_description,
> +                                                
> loadable_compression,
> +                                                 loadable_arch,
> loadable_os,
> +                                                
> loadable_loadaddress,
> +                                                
> loadable_entrypoint)
> +
>      # Generate the configuration section
>     
> root_node.fitimage_emit_section_config(d.getVar("FIT_CONF_DEFAULT_DTB
> "), d.getVar("FIT_CONF_MAPPINGS"))
>  
> diff --git a/meta/conf/image-fitimage.conf b/meta/conf/image-
> fitimage.conf
> index
> 4e7ea2750edba9614e52f983cbc41386e1acf395..2fdb816d556c786dcc2c14cb149
> 446eaddc2ff4b 100644
> --- a/meta/conf/image-fitimage.conf
> +++ b/meta/conf/image-fitimage.conf
> @@ -80,3 +80,21 @@ FIT_ADDRESS_CELLS ?= "1"
>  # Machine configurations needing such a script file should include
> it in the
>  # SRC_URI of the kernel recipe and set the FIT_UBOOT_ENV parameter.
>  FIT_UBOOT_ENV ?= ""
> +
> +# Allow user to insert additional loadable images.
> +# For each loadable, a number of parameters can be defined through
> additional
> +# variable flags.
> +# Example:
> +#   FIT_LOADABLES = "atf"
> +#   FIT_LOADABLE_FILENAME[atf] = "bl31.bin"
> +#   FIT_LOADABLE_COMPRESSSION[atf] = "none"
> +#   FIT_LOADABLE_DESCRIPTION[atf] = "TF-A firmware image"
> +#   FIT_LOADABLE_TYPE[atf] = "tfa-bl31"
> +#   FIT_LOADABLE_ARCH[atf] = "arm64"
> +#   FIT_LOADABLE_OS[atf] = "arm-trusted-firmware"
> +#   FIT_LOADABLE_LOADADDRESS[atf] = "0x204E0000"
> +#   FIT_LOADABLE_ENTRYPOINT[atf] = "0x204E0000"
> +# Sensible defalts will be used for some parameters (compression,
> description,
> +# arch, os) if the corresponding flag is not set, while others (load
> address
> +# and entry point) will be omitted in the final FIT.
> +FIT_LOADABLES ?= ""
> diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py
> index
> 8bf83c0a57823f640c4c0fbc145874561c9b374f..881d0eae0ab0ec427a87031c7d5
> 7608673db193b 100644
> --- a/meta/lib/oe/fitimage.py
> +++ b/meta/lib/oe/fitimage.py
> @@ -195,6 +195,7 @@ class ItsNodeRootKernel(ItsNode):
>          self._ramdisk = None
>          self._bootscr = None
>          self._setup = None
> +        self._loadables = []
>  
>      def _sanitize_sign_config(self):
>          if self._sign_enable:
> @@ -396,6 +397,29 @@ class ItsNodeRootKernel(ItsNode):
>          )
>          self._ramdisk = ramdisk_node
>  
> +    def fitimage_emit_section_loadable(self, name, filepath,
> type=None, description=None, compression=None, arch=None, os=None,
> load=None, entry=None):
> +        """Emit one fitImage ITS loadable section"""
> +        opt_props = {
> +            "data": '/incbin/("' + filepath + '")',
> +            "arch": arch if arch is not None else self._arch,
> +            "os": os if os is not None else "linux",
> +        }
> +
> +        if load:
> +            opt_props["load"] = f"<{load}>"
> +        if entry:
> +            opt_props["entry"] = f"<{entry}>"
> +
> +        loadable_node = self.its_add_node_image(
> +            name,
> +            description if description is not None else name,
> +            type if type is not None else "firmware",
> +            compression if compression is not None else "none",
> +            opt_props
> +        )
> +
> +        self._loadables.append(loadable_node)
> +
>      def _fitimage_emit_one_section_config(self, conf_node_name,
> dtb=None):
>          """Emit the fitImage ITS configuration section"""
>          opt_props = {}
> @@ -434,6 +458,12 @@ class ItsNodeRootKernel(ItsNode):
>              if self._sign_enable:
>                  sign_entries.append("setup")
>  
> +        if len(self._loadables) > 0:
> +            conf_desc.append("loadables")
> +            opt_props["loadables"] = [ loadable.name for loadable in
> self._loadables ]
> +            if self._sign_enable:
> +                sign_entries.append("loadables")
> +
>          # First added configuration is the default configuration
>          default_flag = "0"
>          if len(self.configurations.sub_nodes) == 0:
> diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py
> b/meta/lib/oeqa/selftest/cases/fitimage.py
> index
> 2aff5c84c8fa10c43ccf008859fdb7c4958bfbe7..8151c13d26216c9e83e6f082c0f
> f5379d8a01b34 100644
> --- a/meta/lib/oeqa/selftest/cases/fitimage.py
> +++ b/meta/lib/oeqa/selftest/cases/fitimage.py
> @@ -203,6 +203,15 @@ class FitImageTestCase(OESelftestTestCase):
>              dtb_symlinks.append("am335x-bonegreen-ext-alias.dtb")
>          return (all_dtbs, dtb_symlinks)
>  
> +    @staticmethod
> +    def _get_loadables(bb_vars):
> +        """Return a list of loadable names"""
> +        loadables = []
> +        var_loadables = bb_vars.get('FIT_LOADABLES')
> +        if var_loadables:
> +            loadables += var_loadables.split()
> +        return loadables
> +
>      def _is_req_dict_in_dict(self, found_dict, req_dict):
>          """
>          Check if all key-value pairs in the required dictionary are
> present in the found dictionary.
> @@ -418,6 +427,7 @@ class KernelFitImageBase(FitImageTestCase):
>              'FIT_DESC',
>              'FIT_HASH_ALG',
>              'FIT_KERNEL_COMP_ALG',
> +            'FIT_LOADABLES',
>              'FIT_SIGN_ALG',
>              'FIT_SIGN_INDIVIDUAL',
>              'FIT_UBOOT_ENV',
> @@ -521,6 +531,7 @@ class KernelFitImageBase(FitImageTestCase):
>          fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
>          initramfs_image = bb_vars['INITRAMFS_IMAGE']
>          initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
> +        loadables = FitImageTestCase._get_loadables(bb_vars)
>          uboot_sign_enable = bb_vars.get('UBOOT_SIGN_ENABLE')
>  
>          # image nodes
> @@ -545,6 +556,9 @@ class KernelFitImageBase(FitImageTestCase):
>          else:
>              not_images.append('ramdisk-1')
>  
> +        if loadables:
> +            images += loadables
> +
>          # configuration nodes (one per DTB, symlink, and mappings)
>          configurations = []
>          if dtb_files:
> @@ -695,6 +709,7 @@ class KernelFitImageBase(FitImageTestCase):
>          fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
>          initramfs_image = bb_vars['INITRAMFS_IMAGE']
>          initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
> +        loadables = FitImageTestCase._get_loadables(bb_vars)
>          uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE']
>          uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
>          uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
> @@ -722,6 +737,10 @@ class KernelFitImageBase(FitImageTestCase):
>                  "Load Address": bb_vars['UBOOT_RD_LOADADDRESS'],
>                  "Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT']
>              }
> +        # Create one section per loadable (do not check the value of
> "Type" as
> +        # flags extraction is not supported for bb_vars)
> +        for loadable in loadables:
> +            req_sections[loadable] = { "Type": None }
>          # Create a configuration section for each DTB
>          if dtb_files:
>              for dtb in dtb_files + dtb_symlinks:
> @@ -741,6 +760,8 @@ class KernelFitImageBase(FitImageTestCase):
>                      }
>                  if initramfs_image and initramfs_image_bundle !=
> "1":
>                      req_sections[conf_name]['Init Ramdisk'] =
> "ramdisk-1"
> +                if loadables:
> +                    req_sections[conf_name]['Loadables'] =
> ",".join(loadables)
>          else:
>              conf_name = bb_vars['FIT_CONF_PREFIX'] +  '1'
>              req_sections[conf_name] = {
> @@ -748,6 +769,8 @@ class KernelFitImageBase(FitImageTestCase):
>              }
>              if initramfs_image and initramfs_image_bundle != "1":
>                  req_sections[conf_name]['Init Ramdisk'] = "ramdisk-
> 1"
> +            if loadables:
> +                req_sections[conf_name]['Loadables'] =
> ",".join(loadables)
>  
>          # Add signing related properties if needed
>          if uboot_sign_enable == "1":
> @@ -832,6 +855,8 @@ class
> KernelFitImageRecipeTests(KernelFitImageBase):
>                       in the Image Tree Source. Not all the fields
> are tested,
>                       only the key fields that wont vary between
> different
>                       architectures.
> +                     3. The type value of each loadable is as
> expected in the
> +                     Image Tree Source.
>          Product:     oe-core
>          Author:      Usama Arif <[email protected]>
>          """
> @@ -849,6 +874,14 @@ UBOOT_LOADADDRESS = "0x80080000"
>  UBOOT_ENTRYPOINT = "0x80080000"
>  FIT_DESC = "A model description"
>  FIT_CONF_PREFIX = "foo-"
> +# Use the linux.bin kernel image as loadable file to avoid building
> other components
> +FIT_LOADABLES = "loadable1 loadable2"
> +FIT_LOADABLE_FILENAME[loadable1] = "linux.bin"
> +FIT_LOADABLE_LOADADDRESS[loadable1] = "0x86000000"
> +FIT_LOADABLE_TYPE[loadable1] = "firmware"
> +FIT_LOADABLE_FILENAME[loadable2] = "linux.bin"
> +FIT_LOADABLE_LOADADDRESS[loadable2] = "0x87000000"
> +FIT_LOADABLE_TYPE[loadable2] = "firmware"
>  """
>          config = self._config_add_kernel_classes(config)
>          self.write_config(config)
> @@ -1123,6 +1156,7 @@ class FitImagePyTests(KernelFitImageBase):
>              'FIT_KEY_GENRSA_ARGS': "-F4",
>              'FIT_KEY_REQ_ARGS': "-batch -new",
>              'FIT_KEY_SIGN_PKCS': "-x509",
> +            'FIT_LOADABLES': "",
>              'FIT_LINUX_BIN': "linux.bin",
>              'FIT_PAD_ALG': "pkcs-1.5",
>              'FIT_SIGN_ALG': "rsa2048",
> @@ -1198,6 +1232,12 @@ class FitImagePyTests(KernelFitImageBase):
>                  "core-image-minimal-initramfs",
>                  bb_vars.get("UBOOT_RD_LOADADDRESS"),
> bb_vars.get("UBOOT_RD_ENTRYPOINT"))
>  
> +        loadables = FitImageTestCase._get_loadables(bb_vars)
> +        for loadable in loadables:
> +            root_node.fitimage_emit_section_loadable(loadable,
> +                "a-dir/loadable-%s" % loadable,
> +                "loadable-type")
> +
>         
> root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB'
> ], bb_vars.get('FIT_CONF_MAPPINGS'))
>          root_node.write_its_file(fitimage_its_path)
>  
> @@ -1254,6 +1294,13 @@ class FitImagePyTests(KernelFitImageBase):
>          with self.assertRaises(BBHandledException):
>              self._test_fitimage_py(bb_vars_overrides)
>  
> +    def test_fitimage_py_conf_loadables(self):
> +        """Test FIT_LOADABLES basic functionality"""
> +        bb_vars_overrides = {
> +            'FIT_LOADABLES': "my-loadable",
> +        }
> +        self._test_fitimage_py(bb_vars_overrides)
> +
>  
>  class UBootFitImageTests(FitImageTestCase):
>      """Test cases for the uboot-sign bbclass"""
> 
> 
> 
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#232143): 
https://lists.openembedded.org/g/openembedded-core/message/232143
Mute This Topic: https://lists.openembedded.org/mt/118051507/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to