[1] https://embedded-recipes.org/2025/images/slides/er-2025-vasut.pdf
---
Changes in v2:
- Added check over direct use of FIT_LOADABLE_* variables.
- Dropped patch for "relaxed" check of FIT nodes, superseeded by a new
test approach (similar to the one used by uboot-config.bbclass).
- Link to v1:
https://lore.kernel.org/r/[email protected]
---
meta/classes-recipe/kernel-fit-image.bbclass | 48 +++++++++++++++++++++++++
meta/conf/image-fitimage.conf | 18 ++++++++++
meta/lib/oe/fitimage.py | 30 ++++++++++++++++
meta/lib/oeqa/selftest/cases/fitimage.py | 52 ++++++++++++++++++++++++++++
4 files changed, 148 insertions(+)
diff --git a/meta/classes-recipe/kernel-fit-image.bbclass
b/meta/classes-recipe/kernel-fit-image.bbclass
index
4880027b210196d25f83d7e683c37bd5e66575d3..367bc9bcfd3a4cd237e466269fa143941a02031a
100644
--- a/meta/classes-recipe/kernel-fit-image.bbclass
+++ b/meta/classes-recipe/kernel-fit-image.bbclass
@@ -28,6 +28,21 @@ python () {
if providerdtb:
d.appendVarFlag('do_compile', 'depends', '
virtual/dtb:do_populate_sysroot')
d.setVar('EXTERNAL_KERNEL_DEVICETREE',
"${RECIPE_SYSROOT}/boot/devicetree")
+
+ # Parse loadables config
+ loadables = d.getVar('FIT_LOADABLES')
+ if loadables:
+ # Check that the loadables configuration variables are not set directly
+ # directly, then assign them a synthetic value for testability purposes
+ for v in ['FIT_LOADABLE_ARCH', 'FIT_LOADABLE_COMPRESSION',
+ 'FIT_LOADABLE_DESCRIPTION', 'FIT_LOADABLE_ENTRYPOINT',
+ 'FIT_LOADABLE_FILENAME', 'FIT_LOADABLE_LOADADDRESS',
+ 'FIT_LOADABLE_OS', 'FIT_LOADABLE_TYPE']:
+ if d.getVar(v):
+ raise bb.parse.SkipRecipe("You cannot use %s as a variable, you can
only set flags." % v)
+
+ synt_value = " ? ".join([ d.getVarFlag(v, loadable) or "" for
loadable in loadables.split() ])
+ d.setVar(v, synt_value)
}
do_configure[noexec] = "1"
@@ -139,6 +154,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_arch = d.getVarFlag('FIT_LOADABLE_ARCH', loadable)
+ loadable_compression = d.getVarFlag('FIT_LOADABLE_COMPRESSION',
loadable)
+ loadable_description = d.getVarFlag('FIT_LOADABLE_DESCRIPTION', loadable) or
("%s loadable" % loadable)
+ loadable_entrypoint = d.getVarFlag('FIT_LOADABLE_ENTRYPOINT', loadable)
+ loadable_os = d.getVarFlag('FIT_LOADABLE_OS', loadable)
+ loadable_type = d.getVarFlag('FIT_LOADABLE_TYPE', 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
0f2d9d17dbe6a452261566a1421cc41d10809399..3541c07520a8ee3f212471430792c31334d54278
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.
@@ -417,6 +426,9 @@ class KernelFitImageBase(FitImageTestCase):
'FIT_DESC',
'FIT_HASH_ALG',
'FIT_KERNEL_COMP_ALG',
+ 'FIT_LOADABLES',
+ 'FIT_LOADABLE_ENTRYPOINT',
+ 'FIT_LOADABLE_LOADADDRESS',
'FIT_SIGN_ALG',
'FIT_SIGN_INDIVIDUAL',
'FIT_UBOOT_ENV',
@@ -520,6 +532,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
@@ -544,6 +557,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:
@@ -694,6 +710,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']
@@ -721,6 +738,13 @@ class KernelFitImageBase(FitImageTestCase):
"Load Address": bb_vars['UBOOT_RD_LOADADDRESS'],
"Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT']
}
+ # Create one section per loadable
+ for index,loadable in enumerate(loadables):
+ loadaddress =
bb_vars['FIT_LOADABLE_LOADADDRESS'].split("?")[index].strip()
+ req_sections[loadable] = { "Load Address": loadaddress }
+ entrypoint =
bb_vars['FIT_LOADABLE_ENTRYPOINT'].split("?")[index].strip()
+ if entrypoint:
+ req_sections[loadable]['Entry Point'] = entrypoint
# Create a configuration section for each DTB
if dtb_files:
for dtb in dtb_files + dtb_symlinks:
@@ -740,6 +764,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] = {
@@ -747,6 +773,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":
@@ -831,6 +859,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 load address and (if defined) entrypoint address
+ of each loadable are as expected in the Image Tree Source.
Product: oe-core
Author: Usama Arif <[email protected]>
"""
@@ -848,6 +878,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)
@@ -1122,6 +1160,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",
@@ -1197,6 +1236,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)
@@ -1253,6 +1298,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"""
---
base-commit: be8cdcf13a658e9e81ff2f7b71d1c8c37a920ce7
change-id: 20260227-fit_loadables-1f93b9c7a7f2
Best regards,