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]] -=-=-=-=-=-=-=-=-=-=-=-
