* [PATCH 0/2] fitimage: add ability to include arbitrary loadables
@ 2026-02-27 23:37 Francesco Valla
2026-02-27 23:37 ` [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks Francesco Valla
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Francesco Valla @ 2026-02-27 23:37 UTC (permalink / raw)
To: openembedded-core, Adrian Freihofer
Cc: Marek Vasut, Michael Opdenacker, Francesco Valla, Usama Arif
Hello,
this patchset adds the possibility to include arbitrary loadables in a
kernel FIT image and to define all associated parameters (description,
compression, type, arch, os, load address and entry address) through
variables.
Patch 1 simply extends the fitimage test infrastructure to allow
checking for existence of node properties regardless of their values,
while patch 2 contains the new functionality and associated tests,
The idea behind the proposal is to be able to generate FIT images for
complex boot flows, in which components beyond the Linux kernel, its FDT
and an initramfs need to be loaded before the aforementioned Linux
kernel is up and running.
As an example, the setup propose by Marek Vasut in [1] (boot of the
kernel through OP-TEE, with both components being loaded from a single
FIT by U-Boot) could be simply obtained with:
FIT_LOADABLES = "tee"
FIT_LOADABLE_DESCRIPTION[tee] = "OP-TEE"
FIT_LOADABLE_TYPE[tee] = "tee"
FIT_LOADABLE_ARCH = "arm"
FIT_LOADABLE_OS[tee] = "tee"
FIT_LOADABLE_LOADADDRESS[tee] = "0xde000000"
FIT_LOADABLE_ENTRYPOINT[tee] = "0xde000000"
while a more complex flow I'm experimenting on (boot of the OP-TEE and
the kernel through TF-A on the i.MX93, with all components being loaded
from a single FIT by U-Boot SPL after verification) as:
FIT_LOADABLES = "atf tee"
FIT_LOADABLE_FILENAME[atf] = "bl31-imx93.bin"
FIT_LOADABLE_DESCRIPTION[atf] = "TF-A Firmware"
FIT_LOADABLE_TYPE[atf] = "tfa-bl31"
FIT_LOADABLE_OS[atf] = "arm-trusted-firmware"
FIT_LOADABLE_LOADADDRESS[atf] = "0x204E0000"
FIT_LOADABLE_FILENAME[tee] = "tee.bin"
FIT_LOADABLE_DESCRIPTION[tee] = "OP-TEE"
FIT_LOADABLE_TYPE[tee] = "tee"
FIT_LOADABLE_OS[tee] = "tee"
FIT_LOADABLE_LOADADDRESS[tee] = "0x96000000"
Being inside the FIT image, and part of all configurations, the
loadables can be in this way hashed and (optionally) signed and/or
encrypted with the same flow and key(s) already in place for the kernel.
The generated FIT image is compatible with the U-Boot FIT "full" boot
flow, which loads any component part of the "loadables" group after the
kernel, the fdt and the initramfs.
Regards,
Francesco Valla
[1] https://embedded-recipes.org/2025/images/slides/er-2025-vasut.pdf
Signed-off-by: Francesco Valla <francesco@valla.it>
---
Francesco Valla (2):
oe-selftest: fitimage: allow relaxed node checks
kernel-fit-image: support arbitrary loadables
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 | 50 +++++++++++++++++++++++++++-
4 files changed, 130 insertions(+), 1 deletion(-)
---
base-commit: be8cdcf13a658e9e81ff2f7b71d1c8c37a920ce7
change-id: 20260227-fit_loadables-1f93b9c7a7f2
Best regards,
--
Francesco Valla <francesco@valla.it>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks 2026-02-27 23:37 [PATCH 0/2] fitimage: add ability to include arbitrary loadables Francesco Valla @ 2026-02-27 23:37 ` Francesco Valla 2026-03-01 14:20 ` [OE-core] " adrian.freihofer 2026-02-27 23:37 ` [PATCH 2/2] kernel-fit-image: support arbitrary loadables Francesco Valla 2026-03-01 10:44 ` [PATCH 0/2] fitimage: add ability to include " Michael Opdenacker 2 siblings, 1 reply; 7+ messages in thread From: Francesco Valla @ 2026-02-27 23:37 UTC (permalink / raw) To: openembedded-core, Adrian Freihofer Cc: Marek Vasut, Michael Opdenacker, Francesco Valla Add the ability to test for the existence of a property in a FIT image node without enforcing a specific value. This allows to ensure that properties for which a value cannot be determined (e.g.: because they are arch- or machine-dependent) are anyhow present. Signed-off-by: Francesco Valla <francesco@valla.it> --- meta/lib/oeqa/selftest/cases/fitimage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 0f2d9d17dbe6a452261566a1421cc41d10809399..2aff5c84c8fa10c43ccf008859fdb7c4958bfbe7 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py @@ -209,6 +209,7 @@ class FitImageTestCase(OESelftestTestCase): This function recursively checks if the required dictionary (`req_dict`) is a subset of the found dictionary (`found_dict`). It supports nested dictionaries, strings, lists, and sets as values. + If the value of one element in the required dictionary is None, only the presence of the key is checked. Args: found_dict (dict): The dictionary to search within. @@ -224,7 +225,7 @@ class FitImageTestCase(OESelftestTestCase): self.assertLessEqual(set(value), set(found_dict[key])) elif isinstance(value, set): self.assertLessEqual(value, found_dict[key]) - else: + elif value is not None: self.assertEqual(value, found_dict[key]) def _check_its_file(self, bb_vars, its_file_path): -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [OE-core] [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks 2026-02-27 23:37 ` [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks Francesco Valla @ 2026-03-01 14:20 ` adrian.freihofer 2026-03-01 19:31 ` Francesco Valla 0 siblings, 1 reply; 7+ messages in thread From: adrian.freihofer @ 2026-03-01 14:20 UTC (permalink / raw) To: francesco, openembedded-core, Adrian Freihofer Cc: Marek Vasut, Michael Opdenacker Hi Francesco Thank you for the nice patches. Also thank you for separating this change into a separate patch to make it more obvious that this is potentially a bit controversial. This looks like a risky change to me. Having a variable set to None by accident is a common error which the test should catch. Just allowing that is probably not the right approach. Is it really not possible to make the tests providing the correct reference values in the dict which compared against the output? Regards, Adrian On Sat, 2026-02-28 at 00:37 +0100, Francesco Valla via lists.openembedded.org wrote: > Add the ability to test for the existence of a property in a FIT > image > node without enforcing a specific value. This allows to ensure that > properties for which a value cannot be determined (e.g.: because they > are arch- or machine-dependent) are anyhow present. > > Signed-off-by: Francesco Valla <francesco@valla.it> > --- > meta/lib/oeqa/selftest/cases/fitimage.py | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py > b/meta/lib/oeqa/selftest/cases/fitimage.py > index > 0f2d9d17dbe6a452261566a1421cc41d10809399..2aff5c84c8fa10c43ccf008859f > db7c4958bfbe7 100644 > --- a/meta/lib/oeqa/selftest/cases/fitimage.py > +++ b/meta/lib/oeqa/selftest/cases/fitimage.py > @@ -209,6 +209,7 @@ class FitImageTestCase(OESelftestTestCase): > > This function recursively checks if the required dictionary > (`req_dict`) is a subset of the found dictionary (`found_dict`). > It supports nested dictionaries, strings, lists, and sets as > values. > + If the value of one element in the required dictionary is > None, only the presence of the key is checked. > > Args: > found_dict (dict): The dictionary to search within. > @@ -224,7 +225,7 @@ class FitImageTestCase(OESelftestTestCase): > self.assertLessEqual(set(value), > set(found_dict[key])) > elif isinstance(value, set): > self.assertLessEqual(value, found_dict[key]) > - else: > + elif value is not None: > self.assertEqual(value, found_dict[key]) > > def _check_its_file(self, bb_vars, its_file_path): > > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#232120): > https://lists.openembedded.org/g/openembedded-core/message/232120 > Mute This Topic: https://lists.openembedded.org/mt/118051540/4454582 > Group Owner: openembedded-core+owner@lists.openembedded.org > Unsubscribe: > https://lists.openembedded.org/g/openembedded-core/unsub [ > adrian.freihofer@gmail.com] > -=-=-=-=-=-=-=-=-=-=-=- ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [OE-core] [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks 2026-03-01 14:20 ` [OE-core] " adrian.freihofer @ 2026-03-01 19:31 ` Francesco Valla 0 siblings, 0 replies; 7+ messages in thread From: Francesco Valla @ 2026-03-01 19:31 UTC (permalink / raw) To: adrian.freihofer Cc: openembedded-core, Adrian Freihofer, Marek Vasut, Michael Opdenacker Hi Adrian, On Sun, Mar 01, 2026 at 03:20:55PM +0100, adrian.freihofer@gmail.com wrote: > Hi Francesco > > Thank you for the nice patches. Also thank you for separating this > change into a separate patch to make it more obvious that this is > potentially a bit controversial. > > This looks like a risky change to me. Having a variable set to None by > accident is a common error which the test should catch. Just allowing > that is probably not the right approach. > I agree with you TBH. > Is it really not possible to make the tests providing the correct > reference values in the dict which compared against the output? > After some more thinking, I think I got the way in which uboot-config.bbclass (which was recently reworked to support flags on UBOOT_CONFIG_* variables) is approaching the matter. I'll rework the test approach and send a v2. > Regards, > Adrian > Thank you! Regards, Francesco ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/2] kernel-fit-image: support arbitrary loadables 2026-02-27 23:37 [PATCH 0/2] fitimage: add ability to include arbitrary loadables Francesco Valla 2026-02-27 23:37 ` [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks Francesco Valla @ 2026-02-27 23:37 ` Francesco Valla 2026-03-01 14:29 ` [OE-core] " adrian.freihofer 2026-03-01 10:44 ` [PATCH 0/2] fitimage: add ability to include " Michael Opdenacker 2 siblings, 1 reply; 7+ messages in thread From: Francesco Valla @ 2026-02-27 23:37 UTC (permalink / raw) To: openembedded-core, Adrian Freihofer Cc: Marek Vasut, Michael Opdenacker, Francesco Valla, Usama Arif 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 <francesco@valla.it> --- 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..48ccdcf4a9d97a1854446521fd4ed8a020153e87 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..2fdb816d556c786dcc2c14cb149446eaddc2ff4b 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..881d0eae0ab0ec427a87031c7d57608673db193b 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..8151c13d26216c9e83e6f082c0ff5379d8a01b34 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 <usama.arif@arm.com> """ @@ -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""" -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [OE-core] [PATCH 2/2] kernel-fit-image: support arbitrary loadables 2026-02-27 23:37 ` [PATCH 2/2] kernel-fit-image: support arbitrary loadables Francesco Valla @ 2026-03-01 14:29 ` adrian.freihofer 0 siblings, 0 replies; 7+ messages in thread From: adrian.freihofer @ 2026-03-01 14:29 UTC (permalink / raw) To: francesco, openembedded-core, Adrian Freihofer Cc: Marek Vasut, Michael Opdenacker, Usama Arif 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 <francesco@valla.it> > --- > 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 <usama.arif@arm.com> > """ > @@ -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 (#232118): > https://lists.openembedded.org/g/openembedded-core/message/232118 > Mute This Topic: https://lists.openembedded.org/mt/118051507/4454582 > Group Owner: openembedded-core+owner@lists.openembedded.org > Unsubscribe: > https://lists.openembedded.org/g/openembedded-core/unsub [ > adrian.freihofer@gmail.com] > -=-=-=-=-=-=-=-=-=-=-=- ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/2] fitimage: add ability to include arbitrary loadables 2026-02-27 23:37 [PATCH 0/2] fitimage: add ability to include arbitrary loadables Francesco Valla 2026-02-27 23:37 ` [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks Francesco Valla 2026-02-27 23:37 ` [PATCH 2/2] kernel-fit-image: support arbitrary loadables Francesco Valla @ 2026-03-01 10:44 ` Michael Opdenacker 2 siblings, 0 replies; 7+ messages in thread From: Michael Opdenacker @ 2026-03-01 10:44 UTC (permalink / raw) To: Francesco Valla, openembedded-core, Adrian Freihofer Cc: michael.opdenacker, Marek Vasut, Usama Arif Hi Francesco, Thanks for the patches! On 2/28/26 12:37 AM, Francesco Valla wrote: > Hello, > > this patchset adds the possibility to include arbitrary loadables in a > kernel FIT image and to define all associated parameters (description, > compression, type, arch, os, load address and entry address) through > variables. > > Patch 1 simply extends the fitimage test infrastructure to allow > checking for existence of node properties regardless of their values, > while patch 2 contains the new functionality and associated tests, > > The idea behind the proposal is to be able to generate FIT images for > complex boot flows, in which components beyond the Linux kernel, its FDT > and an initramfs need to be loaded before the aforementioned Linux > kernel is up and running. > > As an example, the setup propose by Marek Vasut in [1] (boot of the > kernel through OP-TEE, with both components being loaded from a single > FIT by U-Boot) could be simply obtained with: > > FIT_LOADABLES = "tee" > FIT_LOADABLE_DESCRIPTION[tee] = "OP-TEE" > FIT_LOADABLE_TYPE[tee] = "tee" > FIT_LOADABLE_ARCH = "arm" > FIT_LOADABLE_OS[tee] = "tee" > FIT_LOADABLE_LOADADDRESS[tee] = "0xde000000" > FIT_LOADABLE_ENTRYPOINT[tee] = "0xde000000" > > while a more complex flow I'm experimenting on (boot of the OP-TEE and > the kernel through TF-A on the i.MX93, with all components being loaded > from a single FIT by U-Boot SPL after verification) as: > > FIT_LOADABLES = "atf tee" > > FIT_LOADABLE_FILENAME[atf] = "bl31-imx93.bin" > FIT_LOADABLE_DESCRIPTION[atf] = "TF-A Firmware" > FIT_LOADABLE_TYPE[atf] = "tfa-bl31" > FIT_LOADABLE_OS[atf] = "arm-trusted-firmware" > FIT_LOADABLE_LOADADDRESS[atf] = "0x204E0000" > > FIT_LOADABLE_FILENAME[tee] = "tee.bin" > FIT_LOADABLE_DESCRIPTION[tee] = "OP-TEE" > FIT_LOADABLE_TYPE[tee] = "tee" > FIT_LOADABLE_OS[tee] = "tee" > FIT_LOADABLE_LOADADDRESS[tee] = "0x96000000" > > Being inside the FIT image, and part of all configurations, the > loadables can be in this way hashed and (optionally) signed and/or > encrypted with the same flow and key(s) already in place for the kernel. > > The generated FIT image is compatible with the U-Boot FIT "full" boot > flow, which loads any component part of the "loadables" group after the > kernel, the fdt and the initramfs. This looks good! I guess, I could use this mechanism to replace my "boot-bundle" recipe in meta-riscv (https://github.com/riscv/meta-riscv/blob/master/recipes-bsp/u-boot/boot-bundle.bb). The idea implemented by this recipe is to add the compressed kernel and DTB to the FIT image loaded by the first stage bootloader (SPL), when using a vendor SPL with MMC support and a mainline U-Boot that doesn't have storage support yet. This way, the kernel and DTB are preloaded in RAM by the SPL. Using your mechanism, I should be able to add the bootloader binaries to the kernel FIT image, to achieve the same result, but without a custom recipe :) Thanks Michael. -- Root Commit Embedded Linux Training and Consulting https://rootcommit.com ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-01 19:32 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-27 23:37 [PATCH 0/2] fitimage: add ability to include arbitrary loadables Francesco Valla 2026-02-27 23:37 ` [PATCH 1/2] oe-selftest: fitimage: allow relaxed node checks Francesco Valla 2026-03-01 14:20 ` [OE-core] " adrian.freihofer 2026-03-01 19:31 ` Francesco Valla 2026-02-27 23:37 ` [PATCH 2/2] kernel-fit-image: support arbitrary loadables Francesco Valla 2026-03-01 14:29 ` [OE-core] " adrian.freihofer 2026-03-01 10:44 ` [PATCH 0/2] fitimage: add ability to include " Michael Opdenacker
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox