diff mbox series

[08/20] kernel-fit-image.bbclass: add a new FIT image implementation

Message ID 20250504130507.717954-8-adrian.freihofer@siemens.com
State New
Headers show
Series [01/20] oe-selftest: add new ext dtb recipe | expand

Commit Message

Freihofer, Adrian May 4, 2025, 1:04 p.m. UTC
From: Adrian Freihofer <adrian.freihofer@siemens.com>

This new recipe and the new kernel-fit-image.bbclass are intended to
become the successor of the kernel-fitimage.bbclass.

This recipe is an alternative implementation of what the
kernel-fitimage.bbclass provides. Instead of injecting the FIT image
related build steps into the kernel recipe, the new recipe takes the
kernel artifacts from the kernel recipe and creates the FIT image as a
independent task.

This solves some basic problems:
* sstate does not work well when a fitImage contains an initramfs. The
  kernel is rebuilt from scratch if the build runs from an empty TMPDIR.
* A fitImage kernel is not available as a package, but all other kernel
  image types are.
* The task dependencies in the kernel are very complex and difficult to
  debug if something goes wrong. As a separate, downstream recipe, this
  is now much easier.

The long storry about this issue is here:
[YOCTO #12912]

Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
 meta/classes-recipe/kernel-fit-image.bbclass  | 206 ++++++++
 meta/lib/oe/fitimage.py                       | 481 ++++++++++++++++++
 .../linux/linux-yocto-fitimage_6.12.bb        |  13 +
 3 files changed, 700 insertions(+)
 create mode 100644 meta/classes-recipe/kernel-fit-image.bbclass
 create mode 100644 meta/lib/oe/fitimage.py
 create mode 100644 meta/recipes-kernel/linux/linux-yocto-fitimage_6.12.bb

Comments

Freihofer, Adrian May 4, 2025, 1:23 p.m. UTC | #1
Hi

This patch series was submitted without a --cover-letter. Please
consider this response to the main patch of this series as the cover
letter.

This patch series re-writes the FIT image related code. The goal is to
fix [YOCTO #12912] which is a long standing issue.

What gets fixed
---------------

* sstate does not work well if a FIT image contains an initramfs.
  The kernel gets re-built from scratch if the build runs from an
  empty TMPDIR:
  https://lists.openembedded.org/g/openembedded-core/message/203510.
  This is also problematic for SDK use cases since working with the
  SDK is annoying if the kernel gets re-built from scratch for no
  good reason.
* A FIT image kernel is not available as a package, but all other
  kernel image types are.
* The its-file is generated by complicated shell code with lots of
  code duplications. Switching to Python simplifies the maintenance
  (also because of the indentation with spaces and tabs in the shell
  code).
* Separating the FIT image related complexity from the kernel build
  complexity simplifies the maintenance of the kernel but also of
  the FIT image related code.
* There is already a new (but unfortunately completely unaligned)
  implementation for creating FIT images in meta-openembedded:
 
https://github.com/openembedded/meta-openembedded/blob/master/meta-oe/
  classes/fitimage.bbclass. Let's hope this patch series will lay a
  solid foundation for a future merge of the two implementations.
* The new implementation in Python is also a preparation for additional
  features, such as adding different types of artifacts or generating
  the configuration nodes with greater flexibility. Currently, exactly
  one configuration per device node is supported.

Architectural change
--------------------

The existing kernel-fitimage.bbclass is designed to be added to
KERNEL_CLASSES. It appends code to the kernel's tasks and injects
additional tasks between the existing ones. Some functions rely on
running within the kernel's build folder structure.

The new implementation introduces the kernel-fit-image.bbclass,
which is intended to be inherited by an independent recipe. This
recipe takes the kernel artifacts from the sstate-cache and assembles
the FIT image entirely independently of the kernel's build tasks and
directory structure.

An example of using the new kernel-fit-image.bbclass is the
linux-yocto-fitimage.bb recipe, which builds the FIT image for the
linux-yocto kernel. The recipe looks like this:

  SUMMARY = "The Linux kernel as a FIT image (optionally with
initramfs)"
  SECTION = "kernel"
  LICENSE = "GPL-2.0-with-Linux-syscall-note"
  LIC_FILES_CHKSUM = "\
    file://${COREBASE}/meta/files/common-licenses/GPL-2.0-with-Linux-
syscall-note;
    md5=0bad96c422c41c3a94009dcfe1bff992"

  inherit kernel-fit-image

The configuration variables defined in the conf/image-fitimage.conf
file are handled by the kernel-fit-image.bbclass in the same way as
they are by the kernel-fitimage.bbclass. The new implementation is
98% backward compatible with the existing kernel-fitimage.bbclass.
The existing test-suite runs with minimal chagnes.

With the kernel-fitimage.bbclass, the FIT image was built as part of
the kernel itself. To ensure the new recipe is built automatically,
the following variables can be set, for example, in the machine
configuration file:

  # Do not install the kernel image package
  RRECOMMENDS:${KERNEL_PACKAGE_NAME}-base = ""

  # Install the FIT image package into the rootfs (there is now a
package :-)
  MACHINE_EXTRA_RDEPENDS += "${PREFERRED_PROVIDER_virtual/kernel}-
fitimage"

  # Configure the image.bbclass to depend on the fitImage instead of
only
  # the kernel to ensure the FIT image is built with the image
  KERNEL_DEPLOY_DEPEND = "${PREFERRED_PROVIDER_virtual/kernel}-
fitimage:do_deploy"

Breaking changes
----------------

* Currently it is possible to bundle an initramfs into the kernel and
  finally add this bundle to a FIT image. With this patch series
  completely merged, the INITRAMFS_IMAGE_BUNDLE variable will not have
  an impact on the FIT image build process. Of course, nothing changes
  on the kernel build code. It is still possible to bundle an initramfs
  into the kernel. Just adding this bundle also to the FIT image is not
  possible anymore. It would be possible to bring this feature back by
  slightly modifying the new implementation. But it would make the
  implementation more complex and I don't see a real use case for this
  feature. Bundling an initramfs and a kernel is basically what the FIT
  image is supposed to do natively, without the help of the kernel
build
  code. As long as the kernel build code deals with the initramfs,
using
  a kernel from the sstate-cache is not possible without re-building
the
  kernel. That's why this patch series removes the support for this
  feature.
* Building a kernel FIT image changes.
  Before this patch series:

    A configuration like:
      KERNEL_IMAGETYPE = "fitImage"
      KERNEL_CLASSES = "kernel-fitimage"
    and building a kernel like:
      bitbake linux-yocto
    generated a FIT image including the kernel and maybe additional
    artifacts.

  With this patch series merged, the same can be achieved like this:
    bitbake linux-yocto-fitimage

  While this simple example is even simpler than before, there are
  also other examples which might look more complicated with the
  new implementation than with the old implementation.

Proposal for merging these patch series
---------------------------------------

The patches are split into small steps which allow a step-by-step
merging:

1. Add the new implementation without touching the old implementation

   * oe-selftest: add new ext dtb recipe
   * oe-selftest: fitimage: test coverage for ext dtb
   * kernel-uboot: do not require the kernel build folder
   * kernel-devicetree: export dtbs to sysroot
   * kernel.bbclass: stage the sysroot-only folder
   * kernel-uboot.bbclass: stage the kernel binary
   * kernel-fitimage: refactor order in its
   * kernel-fit-image.bbclass: add a new FIT image implementation
   * maintainers: add myself for linux-yocto-fitimage
   * oe-selftest: fitimage add tests for fitimage.py
   * oe-selftest: fitimage refactor kernel-fitimage.bbclass
   * oe-selftest: fitimage: run all tests for the FIT image recipe
   * oe-selftest: fitimage refactor classes

2. Refactor the old kernel-fitimage.bbclass to Python to share code
   with the new implementation. While not strictly required, this
   allows users to migrate to the new Python code with 100% backward
   compatibility.
   Both implementations could be maintained until the new one is widely
   accepted and tested.
   Note: This change does obviousely not resolve any issues as the
   architecture remains the same. We can also simply drop this patch.

   * kernel-fitimage: re-write its code in Python

3. Remove the old kernel-fitimage.bbclass and clean up the code from
   the initramfs bundle in the FIT image left overs.

   * kernel.bbclass: remove support for type fitImage
   * oe-selftest: fitimage: remove kernel-fitimage tests
   * kernel-fitimage.bbclass: remove it
   * kernel-uboot.bbclass: merge it into kernel-uimage.bbclass
   * oe-selftest: fitimage: drop initramfs bundle test
   * kernel-fit-image.bbclass: ignore INITRAMFS_IMAGE_BUNDLE

Side note: The development of the commits took place the other way
round:
First the existing implementation was re-written in Python. Then the
new implementation was split out and finally the independent recipe
could be added to use the new kernel-fit-image.bbclass as well.

Some documentation patches are under development on a branch:
https://git.yoctoproject.org/poky-contrib/log/?h=adrianf/fitimage-improvements

Regards,
Adrian
Richard Purdie May 4, 2025, 5:21 p.m. UTC | #2
On Sun, 2025-05-04 at 15:04 +0200, AdrianF wrote:
> From: Adrian Freihofer <adrian.freihofer@siemens.com>
> 
> This new recipe and the new kernel-fit-image.bbclass are intended to
> become the successor of the kernel-fitimage.bbclass.
> 
> This recipe is an alternative implementation of what the
> kernel-fitimage.bbclass provides. Instead of injecting the FIT image
> related build steps into the kernel recipe, the new recipe takes the
> kernel artifacts from the kernel recipe and creates the FIT image as a
> independent task.
> 
> This solves some basic problems:
> * sstate does not work well when a fitImage contains an initramfs. The
>   kernel is rebuilt from scratch if the build runs from an empty TMPDIR.
> * A fitImage kernel is not available as a package, but all other kernel
>   image types are.
> * The task dependencies in the kernel are very complex and difficult to
>   debug if something goes wrong. As a separate, downstream recipe, this
>   is now much easier.
> 
> The long storry about this issue is here:
> [YOCTO #12912]
> 
> Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>

Since the autobuilder is quiet, I put this in for testing. Two failures
so far, both due to world builds:

https://autobuilder.yoctoproject.org/valkyrie/#/builders/59/builds/1521

I suspect this should have an EXCLUDE_FROM_WORLD so it doesn't get
pulled in unless configured?

Cheers,

Richard
Freihofer, Adrian May 4, 2025, 9:39 p.m. UTC | #3
On Sun, 2025-05-04 at 18:21 +0100, Richard Purdie wrote:
> On Sun, 2025-05-04 at 15:04 +0200, AdrianF wrote:
> > From: Adrian Freihofer <adrian.freihofer@siemens.com>
> >
> > This new recipe and the new kernel-fit-image.bbclass are intended
> > to
> > become the successor of the kernel-fitimage.bbclass.
> >
> > This recipe is an alternative implementation of what the
> > kernel-fitimage.bbclass provides. Instead of injecting the FIT
> > image
> > related build steps into the kernel recipe, the new recipe takes
> > the
> > kernel artifacts from the kernel recipe and creates the FIT image
> > as a
> > independent task.
> >
> > This solves some basic problems:
> > * sstate does not work well when a fitImage contains an initramfs.
> > The
> >   kernel is rebuilt from scratch if the build runs from an empty
> > TMPDIR.
> > * A fitImage kernel is not available as a package, but all other
> > kernel
> >   image types are.
> > * The task dependencies in the kernel are very complex and
> > difficult to
> >   debug if something goes wrong. As a separate, downstream recipe,
> > this
> >   is now much easier.
> >
> > The long storry about this issue is here:
> > [YOCTO #12912]
> >
> > Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
>
> Since the autobuilder is quiet, I put this in for testing. Two
> failures
> so far, both due to world builds:
>
Thank you!
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/59/builds/1521
>
> I suspect this should have an EXCLUDE_FROM_WORLD so it doesn't get
> pulled in unless configured?

bitbake linux-yocto-fitimage
works with the default build configuration. So my guess is that also
bitbake world should work.

The failing build configuration seems to use this multiconfig fragment:
https://web.git.yoctoproject.org/poky/tree/meta/conf/fragments/yocto-autobuilder/multilib-x86-lib32.conf
which brings us to
  bitbake lib32-linux-yocto-fitimage
which looks like something which cannot work to me.

It is unclear what the correct exclusion should be. Ideally,
linux-yocto-fitimage should build with the default configuration but
not when using `multilib-x86-lib32.conf`.

Adrian


>
> Cheers,
>
> Richard
Richard Purdie May 5, 2025, 7:19 a.m. UTC | #4
On Sun, 2025-05-04 at 21:39 +0000, Freihofer, Adrian wrote:
> On Sun, 2025-05-04 at 18:21 +0100, Richard Purdie wrote:
> > On Sun, 2025-05-04 at 15:04 +0200, AdrianF wrote:
> > > From: Adrian Freihofer <adrian.freihofer@siemens.com>
> > > 
> > > This new recipe and the new kernel-fit-image.bbclass are intended
> > > to
> > > become the successor of the kernel-fitimage.bbclass.
> > > 
> > > This recipe is an alternative implementation of what the
> > > kernel-fitimage.bbclass provides. Instead of injecting the FIT
> > > image
> > > related build steps into the kernel recipe, the new recipe takes
> > > the
> > > kernel artifacts from the kernel recipe and creates the FIT image
> > > as a
> > > independent task.
> > > 
> > > This solves some basic problems:
> > > * sstate does not work well when a fitImage contains an initramfs.
> > > The
> > >   kernel is rebuilt from scratch if the build runs from an empty
> > > TMPDIR.
> > > * A fitImage kernel is not available as a package, but all other
> > > kernel
> > >   image types are.
> > > * The task dependencies in the kernel are very complex and
> > > difficult to
> > >   debug if something goes wrong. As a separate, downstream recipe,
> > > this
> > >   is now much easier.
> > > 
> > > The long storry about this issue is here:
> > > [YOCTO #12912]
> > > 
> > > Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
> > 
> > Since the autobuilder is quiet, I put this in for testing. Two
> > failures
> > so far, both due to world builds:
> > 
> Thank you!
> > https://autobuilder.yoctoproject.org/valkyrie/#/builders/59/builds/1521
> > 
> > I suspect this should have an EXCLUDE_FROM_WORLD so it doesn't get
> > pulled in unless configured?
> 
> bitbake linux-yocto-fitimage
> works with the default build configuration. So my guess is that also
> bitbake world should work.
> 
> The failing build configuration seems to use this multiconfig fragment:
> https://web.git.yoctoproject.org/poky/tree/meta/conf/fragments/yocto-autobuilder/multilib-x86-lib32.conf
> which brings us to
>   bitbake lib32-linux-yocto-fitimage
> which looks like something which cannot work to me.
> 
> It is unclear what the correct exclusion should be. Ideally,
> linux-yocto-fitimage should build with the default configuration but
> not when using `multilib-x86-lib32.conf`.

I hadn't looked into the failure in detail but this explains why some
world builds were ok and some were not. It sounds like we want to
exclude it from multilib extensions. There is a variable listing these,
much as I dislike doing so:

conf/multilib.conf:NON_MULTILIB_RECIPES = "grub grub-efi make-mod-scripts ovmf u-boot gcc-source llvm-project-source"

There is also a second issue on ubuntu 20.04 systems using buildtools:

https://autobuilder.yoctoproject.org/valkyrie/#/builders/23/builds/1591
https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/1478

Some of the failures are network/server problems but:

fitimage.FitImagePyTests.test_fitimage_py_generate_keys_one: FAILED (0.20s)
2025-05-04 20:11:24,809 - oe-selftest - INFO - RESULTS - fitimage.FitImagePyTests.test_fitimage_py_generate_keys_two: FAILED (0.11s)

look related to this series. It looks like we're calling openssl
without the right environment variables set.

Those 20.04 systems have buildtools installed on them.

Cheers,

Richard
diff mbox series

Patch

diff --git a/meta/classes-recipe/kernel-fit-image.bbclass b/meta/classes-recipe/kernel-fit-image.bbclass
new file mode 100644
index 00000000000..3c40fbb0ffa
--- /dev/null
+++ b/meta/classes-recipe/kernel-fit-image.bbclass
@@ -0,0 +1,206 @@ 
+
+inherit kernel-arch kernel-artifact-names uboot-config deploy
+require conf/image-fitimage.conf
+
+S = "${WORKDIR}/sources"
+UNPACKDIR = "${S}"
+
+PACKAGE_ARCH = "${MACHINE_ARCH}"
+
+DEPENDS += "u-boot-tools-native dtc-native virtual/kernel"
+
+python () {
+    image = d.getVar('INITRAMFS_IMAGE')
+    if image and not bb.utils.to_boolean(d.getVar('INITRAMFS_IMAGE_BUNDLE')):
+        if d.getVar('INITRAMFS_MULTICONFIG'):
+            mc = d.getVar('BB_CURRENT_MC')
+            d.appendVarFlag('do_compile', 'mcdepends', ' mc:' + mc + ':${INITRAMFS_MULTICONFIG}:${INITRAMFS_IMAGE}:do_image_complete')
+        else:
+            d.appendVarFlag('do_compile', 'depends', ' ${INITRAMFS_IMAGE}:do_image_complete')
+
+    #check if there are any dtb providers
+    providerdtb = d.getVar("PREFERRED_PROVIDER_virtual/dtb")
+    if providerdtb:
+        d.appendVarFlag('do_compile', 'depends', ' virtual/dtb:do_populate_sysroot')
+        d.setVar('EXTERNAL_KERNEL_DEVICETREE', "${RECIPE_SYSROOT}/boot/devicetree")
+
+    # Maybe more for develpoment purpose, support generating a set of keys
+    if oe.types.boolean(d.getVar('UBOOT_SIGN_ENABLE')) and oe.types.boolean(d.getVar('FIT_GENERATE_KEYS')):
+        bb.build.addtask('do_generate_rsa_keys', 'do_compile', '', d)
+}
+
+do_configure[noexec] = "1"
+
+UBOOT_MKIMAGE_KERNEL_TYPE ?= "kernel"
+KERNEL_IMAGEDEST ?= "/boot"
+
+python do_compile() {
+    import shutil
+    import oe.fitimage
+
+    itsfile = "fit-image.its"
+    fitname = "fitImage"
+    recipe_sysroot = d.getVar('RECIPE_SYSROOT')
+
+    # Collect all the its nodes before the its file is generated and mkimage gets executed
+    root_node = oe.fitimage.ItsNodeRootKernel(
+        d.getVar("FIT_DESC"), d.getVar("FIT_ADDRESS_CELLS"),
+        d.getVar('HOST_PREFIX'), d.getVar('UBOOT_ARCH'),  d.getVar("FIT_CONF_PREFIX"),
+        oe.types.boolean(d.getVar('UBOOT_SIGN_ENABLE')), d.getVar("UBOOT_SIGN_KEYDIR"),
+        d.getVar("UBOOT_MKIMAGE"), d.getVar("UBOOT_MKIMAGE_DTCOPTS"),
+        d.getVar("UBOOT_MKIMAGE_SIGN"), d.getVar("UBOOT_MKIMAGE_SIGN_ARGS"),
+        d.getVar('FIT_HASH_ALG'), d.getVar('FIT_SIGN_ALG'), d.getVar('FIT_PAD_ALG'),
+        d.getVar('UBOOT_SIGN_KEYNAME'),
+        oe.types.boolean(d.getVar('FIT_SIGN_INDIVIDUAL')), d.getVar('UBOOT_SIGN_IMG_KEYNAME')
+    )
+
+    # Prepare a kernel image section.
+    shutil.copyfile(os.path.join(recipe_sysroot, "sysroot-only", "linux.bin"), "linux.bin")
+    with open(os.path.join(recipe_sysroot, "sysroot-only", "linux_comp")) as linux_comp_f:
+        linux_comp = linux_comp_f.read()
+    root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", linux_comp,
+        d.getVar('UBOOT_LOADADDRESS'), d.getVar('UBOOT_ENTRYPOINT'),
+        d.getVar('UBOOT_MKIMAGE_KERNEL_TYPE'), d.getVar("UBOOT_ENTRYSYMBOL"))
+
+    # Prepare a DTB image section
+    kernel_devicetree = d.getVar('KERNEL_DEVICETREE')
+    external_kernel_devicetree = d.getVar("EXTERNAL_KERNEL_DEVICETREE")
+    if kernel_devicetree:
+        for dtb in kernel_devicetree.split():
+            dtb_name = os.path.basename(dtb)
+
+            # Skip DTB if it's also provided in EXTERNAL_KERNEL_DEVICETREE directory
+            if external_kernel_devicetree:
+                ext_dtb_path = os.path.join(d.getVar("EXTERNAL_KERNEL_DEVICETREE"), dtb_name)
+                if os.path.exists(ext_dtb_path) and os.path.getsize(ext_dtb_path) > 0:
+                    continue
+
+            # Copy the dtb or dtbo file into the FIT image assembly directory
+            shutil.copyfile(os.path.join(recipe_sysroot, "sysroot-only", dtb_name), dtb_name)
+            root_node.fitimage_emit_section_dtb(dtb_name, dtb_name,
+                d.getVar("UBOOT_DTB_LOADADDRESS"), d.getVar("UBOOT_DTBO_LOADADDRESS"))
+
+    if external_kernel_devicetree:
+        # iterate over all .dtb and .dtbo files in the external kernel devicetree directory
+        # and copy them to the FIT image assembly directory
+        for dtb_name in os.listdir(external_kernel_devicetree):
+            if dtb_name.endswith('.dtb') or dtb_name.endswith('.dtbo'):
+                dtb_path = os.path.join(external_kernel_devicetree, dtb_name)
+
+                # Also skip if a symlink. We'll later have each config section point at it
+                if oe.fitimage.symlink_points_below(dtb_name, external_kernel_devicetree):
+                    continue
+
+                shutil.copyfile(dtb_path, dtb_name)
+                root_node.fitimage_emit_section_dtb(dtb_name, dtb_name,
+                    d.getVar("UBOOT_DTB_LOADADDRESS"), d.getVar("UBOOT_DTBO_LOADADDRESS"))
+
+    # Prepare a u-boot script section
+    fit_uboot_env = d.getVar("FIT_UBOOT_ENV")
+    if fit_uboot_env:
+        root_node.fitimage_emit_section_boot_script("bootscr-"+fit_uboot_env , fit_uboot_env)
+
+    # Prepare a setup section (For x86)
+    setup_bin_path = os.path.join(recipe_sysroot, "sysroot-only", "setup.bin")
+    if os.path.exists(setup_bin_path):
+        shutil.copyfile(setup_bin_path, "setup.bin")
+        root_node.fitimage_emit_section_setup("setup-1", "setup.bin")
+
+    # Prepare a ramdisk section.
+    initramfs_image = d.getVar('INITRAMFS_IMAGE')
+    if initramfs_image and not oe.types.boolean(d.getVar("INITRAMFS_IMAGE_BUNDLE")):
+        # Find and use the first initramfs image archive type we find
+        found = False
+        for img in d.getVar("FIT_SUPPORTED_INITRAMFS_FSTYPES").split():
+            initramfs_path = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), "%s.%s" % (d.getVar('INITRAMFS_IMAGE_NAME'), img))
+            if os.path.exists(initramfs_path):
+                bb.note("Found initramfs image: " + initramfs_path)
+                found = True
+                root_node.fitimage_emit_section_ramdisk("ramdisk-1", initramfs_path,
+                    initramfs_image,
+                    d.getVar("UBOOT_RD_LOADADDRESS"),
+                    d.getVar("UBOOT_RD_ENTRYPOINT"))
+                break
+            else:
+                bb.note("Did not find initramfs image: " + initramfs_path)
+
+        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')))
+
+    # Generate the configuration section
+    root_node.fitimage_emit_section_config()
+
+    # Write the its file
+    root_node.write_its_file(itsfile)
+
+    # Assemble the FIT image
+    root_node.run_mkimage_assemble(itsfile, fitname)
+
+    # Sign the FIT image if required
+    root_node.run_mkimage_sign(fitname)
+}
+
+do_install() {
+    install -d "${D}/${KERNEL_IMAGEDEST}"
+    install -m 0644 "${B}/fitImage" "${D}/${KERNEL_IMAGEDEST}/fitImage"
+}
+
+FILES:${PN} = "${KERNEL_IMAGEDEST}"
+
+
+do_deploy() {
+    deploy_dir="${DEPLOYDIR}"
+    if [ -n "${KERNEL_DEPLOYSUBDIR}" ]; then
+        deploy_dir="${DEPLOYDIR}/${KERNEL_DEPLOYSUBDIR}"
+    fi
+    install -d "$deploy_dir"
+    install -m 0644 "${B}/fitImage" "$deploy_dir/fitImage"
+    install -m 0644 "${B}/fit-image.its" "$deploy_dir/fit-image.its"
+
+    if [ "${INITRAMFS_IMAGE_BUNDLE}" != "1" ]; then
+        ln -snf fit-image.its "$deploy_dir/fitImage-its-${KERNEL_FIT_NAME}.its"
+        if [ -n "${KERNEL_FIT_LINK_NAME}" ] ; then
+            ln -snf fit-image.its "$deploy_dir/fitImage-its-${KERNEL_FIT_LINK_NAME}"
+        fi
+    fi
+
+    if [ -n "${INITRAMFS_IMAGE}" ]; then
+        ln -snf fit-image-its "$deploy_dir/fitImage-its-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_NAME}.its"
+        if [ -n "${KERNEL_FIT_LINK_NAME}" ]; then
+            ln -snf fit-image.its "$deploy_dir/fitImage-its-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_LINK_NAME}"
+        fi
+
+        if [ "${INITRAMFS_IMAGE_BUNDLE}" != "1" ]; then
+            ln -snf fitImage "$deploy_dir/fitImage-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_NAME}${KERNEL_FIT_BIN_EXT}"
+            if [ -n "${KERNEL_FIT_LINK_NAME}" ] ; then
+                ln -snf fitImage "$deploy_dir/fitImage-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_LINK_NAME}"
+            fi
+        fi
+    fi
+}
+addtask deploy after do_compile before do_build
+
+
+# Genreate some signing keys if FIT_GENERATE_KEYS is enabled.
+python do_generate_rsa_keys() {
+    import oe.fitimage
+
+    sign_keydir = d.getVar('UBOOT_SIGN_KEYDIR')
+    sign_keyname = d.getVar('UBOOT_SIGN_KEYNAME')
+    sign_kc_path = os.path.join(sign_keydir, sign_keyname)
+    if not os.path.exists(sign_kc_path + '.key') or not os.path.exists(sign_kc_path + '.crt'):
+        bb.note("Generating RSA private key and certificate for signing fitImage configurations")
+        oe.fitimage.generate_rsa_key(sign_keydir, sign_keyname,
+            d.getVar('FIT_SIGN_NUMBITS'), d.getVar('FIT_KEY_GENRSA_ARGS'),
+            d.getVar('FIT_KEY_REQ_ARGS'), d.getVar('FIT_KEY_SIGN_PKCS'))
+
+    if oe.types.boolean(d.getVar('FIT_SIGN_INDIVIDUAL')):
+        sign_img_keyname = d.getVar('UBOOT_SIGN_IMG_KEYNAME')
+        sign_img_kc_path = os.path.join(sign_keydir, sign_img_keyname)
+        if not os.path.exists(sign_img_kc_path + '.key') or not os.path.exists(sign_img_kc_path + '.crt'):
+            bb.note("Generating RSA private key and certificate for signing fitImage individual images")
+            oe.fitimage.generate_rsa_key(sign_keydir, sign_img_keyname,
+                d.getVar('FIT_SIGN_NUMBITS'), d.getVar('FIT_KEY_GENRSA_ARGS'),
+                d.getVar('FIT_KEY_REQ_ARGS'), d.getVar('FIT_KEY_SIGN_PKCS'))
+}
+do_generate_rsa_keys[depends] += "openssl-native:do_populate_sysroot"
diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py
new file mode 100644
index 00000000000..d30d0995ca5
--- /dev/null
+++ b/meta/lib/oe/fitimage.py
@@ -0,0 +1,481 @@ 
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This file contains common functions for the fitimage generation
+
+import os
+import shlex
+import subprocess
+import bb
+
+from oeqa.utils.commands import runCmd
+
+class ItsNode:
+    INDENT_SIZE = 8
+
+    def __init__(self, name, parent_node, sub_nodes=None, properties=None):
+        self.name = name
+        self.parent_node = parent_node
+
+        self.sub_nodes = []
+        if sub_nodes:
+            self.sub_nodes = sub_nodes
+
+        self.properties = {}
+        if properties:
+            self.properties = properties
+
+        if parent_node:
+            parent_node.add_sub_node(self)
+
+    def add_sub_node(self, sub_node):
+        self.sub_nodes.append(sub_node)
+
+    def add_property(self, key, value):
+        self.properties[key] = value
+
+    def emit(self, f, indent):
+        indent_str_name = " " * indent
+        indent_str_props = " " * (indent + self.INDENT_SIZE)
+        f.write("%s%s {\n" % (indent_str_name, self.name))
+        for key, value in self.properties.items():
+            bb.debug(1, "key: %s, value: %s" % (key, str(value)))
+            # Single integer: <0x12ab>
+            if isinstance(value, int):
+                f.write(indent_str_props + key + ' = <0x%x>;\n' % value)
+            # list of strings: "string1", "string2" or integers: <0x12ab 0x34cd>
+            elif isinstance(value, list):
+                if len(value) == 0:
+                    f.write(indent_str_props + key + ' = "";\n')
+                elif isinstance(value[0], int):
+                    list_entries = ' '.join('0x%x' % entry for entry in value)
+                    f.write(indent_str_props + key + ' = <%s>;\n' % list_entries)
+                else:
+                    list_entries = ', '.join('"%s"' % entry for entry in value)
+                    f.write(indent_str_props + key + ' = %s;\n' % list_entries)
+            elif isinstance(value, str):
+                # path: /incbin/("path/to/file")
+                if key in ["data"] and value.startswith('/incbin/('):
+                    f.write(indent_str_props + key + ' = %s;\n' % value)
+                # Integers which are already string formatted
+                elif value.startswith("<") and value.endswith(">"):
+                    f.write(indent_str_props + key + ' = %s;\n' % value)
+                else:
+                    f.write(indent_str_props + key + ' = "%s";\n' % value)
+            else:
+                bb.fatal("%s has unexpexted data type." % str(value))
+        for sub_node in self.sub_nodes:
+            sub_node.emit(f, indent + self.INDENT_SIZE)
+        f.write(indent_str_name + '};\n')
+
+class ItsNodeImages(ItsNode):
+    def __init__(self, parent_node):
+        super().__init__("images", parent_node)
+
+class ItsNodeConfigurations(ItsNode):
+    def __init__(self, parent_node):
+        super().__init__("configurations", parent_node)
+
+class ItsNodeHash(ItsNode):
+    def __init__(self, name, parent_node, algo, opt_props=None):
+        properties = {
+            "algo": algo
+        }
+        if opt_props:
+            properties.update(opt_props)
+        super().__init__(name, parent_node, None, properties)
+
+class ItsImageSignature(ItsNode):
+    def __init__(self, name, parent_node, algo, keyname, opt_props=None):
+        properties = {
+            "algo": algo,
+            "key-name-hint": keyname
+        }
+        if opt_props:
+            properties.update(opt_props)
+        super().__init__(name, parent_node, None, properties)
+
+class ItsNodeImage(ItsNode):
+    def __init__(self, name, parent_node, description, type, compression, sub_nodes=None, opt_props=None):
+        properties = {
+            "description": description,
+            "type": type,
+            "compression": compression,
+        }
+        if opt_props:
+            properties.update(opt_props)
+        super().__init__(name, parent_node, sub_nodes, properties)
+
+class ItsNodeConfigurationSignature(ItsNode):
+    def __init__(self, name, parent_node, algo, keyname, opt_props=None):
+        properties = {
+            "algo": algo,
+            "key-name-hint": keyname
+        }
+        if opt_props:
+            properties.update(opt_props)
+        super().__init__(name, parent_node, None, properties)
+
+class ItsNodeConfiguration(ItsNode):
+    def __init__(self, name, parent_node, description, sub_nodes=None, opt_props=None):
+        properties = {
+            "description": description,
+        }
+        if opt_props:
+            properties.update(opt_props)
+        super().__init__(name, parent_node, sub_nodes, properties)
+
+class ItsNodeRootKernel(ItsNode):
+    """Create FIT images for the kernel
+
+    Currently only a single kernel (no less or more) can be added to the FIT
+    image along with 0 or more device trees and 0 or 1 ramdisk.
+
+    If a device tree included in the FIT image, the default configuration is the
+    firt DTB. If there is no dtb present than the default configuation the kernel.
+    """
+    def __init__(self, description, address_cells, host_prefix, arch, conf_prefix,
+                 sign_enable=False, sign_keydir=None,
+                 mkimage=None, mkimage_dtcopts=None,
+                 mkimage_sign=None, mkimage_sign_args=None,
+                 hash_algo=None, sign_algo=None, pad_algo=None,
+                 sign_keyname_conf=None,
+                 sign_individual=False, sign_keyname_img=None):
+        props = {
+            "description": description,
+            "#address-cells": f"<{address_cells}>"
+        }
+        super().__init__("/", None, None, props)
+        self.images = ItsNodeImages(self)
+        self.configurations = ItsNodeConfigurations(self)
+
+        self._host_prefix = host_prefix
+        self._arch = arch
+        self._conf_prefix = conf_prefix
+
+        # Signature related properties
+        self._sign_enable = sign_enable
+        self._sign_keydir = sign_keydir
+        self._mkimage = mkimage
+        self._mkimage_dtcopts = mkimage_dtcopts
+        self._mkimage_sign = mkimage_sign
+        self._mkimage_sign_args = mkimage_sign_args
+        self._hash_algo = hash_algo
+        self._sign_algo = sign_algo
+        self._pad_algo = pad_algo
+        self._sign_keyname_conf = sign_keyname_conf
+        self._sign_individual = sign_individual
+        self._sign_keyname_img = sign_keyname_img
+        self._sanitize_sign_config()
+
+        self._dtbs = []
+        self._kernel = None
+        self._ramdisk = None
+        self._bootscr = None
+        self._setup = None
+
+    def _sanitize_sign_config(self):
+        if self._sign_enable:
+            if not self._hash_algo:
+                bb.fatal("FIT image signing is enabled but no hash algorithm is provided.")
+            if not self._sign_algo:
+                bb.fatal("FIT image signing is enabled but no signature algorithm is provided.")
+            if not self._pad_algo:
+                bb.fatal("FIT image signing is enabled but no padding algorithm is provided.")
+            if not self._sign_keyname_conf:
+                bb.fatal("FIT image signing is enabled but no configuration key name is provided.")
+            if self._sign_individual and not self._sign_keyname_img:
+                bb.fatal("FIT image signing is enabled for individual images but no image key name is provided.")
+
+    def write_its_file(self, itsfile):
+        with open(itsfile, 'w') as f:
+            f.write("/dts-v1/;\n\n")
+            self.emit(f, 0)
+
+    def its_add_node_image(self, image_id, description, image_type, compression, opt_props):
+        image_node = ItsNodeImage(
+            image_id,
+            self.images,
+            description,
+            image_type,
+            compression,
+            opt_props=opt_props
+        )
+        if self._hash_algo:
+            ItsNodeHash(
+                "hash-1",
+                image_node,
+                self._hash_algo
+            )
+        if self._sign_individual:
+            ItsImageSignature(
+                "signature-1",
+                image_node,
+                f"{self._hash_algo},{self._sign_algo}",
+                self._sign_keyname_img
+            )
+        return image_node
+
+    def fitimage_emit_section_kernel(self, kernel_id, kernel_path, compression,
+        load, entrypoint, mkimage_kernel_type, entrysymbol=None):
+        """Emit the fitImage ITS kernel section"""
+        if self._kernel:
+            bb.fatal("Kernel section already exists in the ITS file.")
+        if entrysymbol:
+            result = subprocess.run([self._host_prefix + "nm", "vmlinux"], capture_output=True, text=True)
+            for line in result.stdout.splitlines():
+                parts = line.split()
+                if len(parts) == 3 and parts[2] == entrysymbol:
+                    entrypoint = "<0x%s>" % parts[0]
+                    break
+        kernel_node = self.its_add_node_image(
+            kernel_id,
+            "Linux kernel",
+            mkimage_kernel_type,
+            compression,
+            {
+                "data": '/incbin/("' + kernel_path + '")',
+                "arch": self._arch,
+                "os": "linux",
+                "load": f"<{load}>",
+                "entry": f"<{entrypoint}>"
+            }
+        )
+        self._kernel = kernel_node
+
+    def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None, dtbo_loadaddress=None):
+        """Emit the fitImage ITS DTB section"""
+        load=None
+        dtb_ext = os.path.splitext(dtb_path)[1]
+        if dtb_ext == ".dtbo":
+            if dtbo_loadaddress:
+                load = dtbo_loadaddress
+        elif dtb_loadaddress:
+            load = dtb_loadaddress
+
+        opt_props = {
+            "data": '/incbin/("' + dtb_path + '")',
+            "arch": self._arch
+        }
+        if load:
+            opt_props["load"] = f"<{load}>"
+
+        dtb_node = self.its_add_node_image(
+            "fdt-" + dtb_id,
+            "Flattened Device Tree blob",
+            "flat_dt",
+            "none",
+            opt_props
+        )
+        self._dtbs.append(dtb_node)
+
+    def fitimage_emit_section_boot_script(self, bootscr_id, bootscr_path):
+        """Emit the fitImage ITS u-boot script section"""
+        if self._bootscr:
+            bb.fatal("U-boot script section already exists in the ITS file.")
+        bootscr_node = self.its_add_node_image(
+            bootscr_id,
+            "U-boot script",
+            "script",
+            "none",
+            {
+                "data": '/incbin/("' + bootscr_path + '")',
+                "arch": self._arch,
+                "type": "script"
+            }
+        )
+        self._bootscr = bootscr_node
+
+    def fitimage_emit_section_setup(self, setup_id, setup_path):
+        """Emit the fitImage ITS setup section"""
+        if self._setup:
+            bb.fatal("Setup section already exists in the ITS file.")
+        load = "<0x00090000>"
+        entry = "<0x00090000>"
+        setup_node = self.its_add_node_image(
+            setup_id,
+            "Linux setup.bin",
+            "x86_setup",
+            "none",
+            {
+                "data": '/incbin/("' + setup_path + '")',
+                "arch": self._arch,
+                "os": "linux",
+                "load": load,
+                "entry": entry
+            }
+        )
+        self._setup = setup_node
+
+    def fitimage_emit_section_ramdisk(self, ramdisk_id, ramdisk_path, description="ramdisk", load=None, entry=None):
+        """Emit the fitImage ITS ramdisk section"""
+        if self._ramdisk:
+            bb.fatal("Ramdisk section already exists in the ITS file.")
+        opt_props = {
+            "data": '/incbin/("' + ramdisk_path + '")',
+            "type": "ramdisk",
+            "arch": self._arch,
+            "os": "linux"
+        }
+        if load:
+            opt_props["load"] = f"<{load}>"
+        if entry:
+            opt_props["entry"] = f"<{entry}>"
+
+        ramdisk_node = self.its_add_node_image(
+            ramdisk_id,
+            description,
+            "ramdisk",
+            "none",
+            opt_props
+        )
+        self._ramdisk = ramdisk_node
+
+    def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None):
+        """Emit the fitImage ITS configuration section"""
+        opt_props = {}
+        conf_desc = []
+        sign_entries = []
+
+        if self._kernel:
+            conf_desc.append("Linux kernel")
+            opt_props["kernel"] = self._kernel.name
+            if self._sign_enable:
+                sign_entries.append("kernel")
+
+        if dtb:
+            conf_desc.append("FDT blob")
+            opt_props["fdt"] = dtb.name
+            if self._sign_enable:
+                sign_entries.append("fdt")
+
+        if self._ramdisk:
+            conf_desc.append("ramdisk")
+            opt_props["ramdisk"] = self._ramdisk.name
+            if self._sign_enable:
+                sign_entries.append("ramdisk")
+
+        if self._bootscr:
+            conf_desc.append("u-boot script")
+            opt_props["bootscr"] = self._bootscr.name
+            if self._sign_enable:
+                sign_entries.append("bootscr")
+
+        if self._setup:
+            conf_desc.append("setup")
+            opt_props["setup"] = self._setup.name
+            if self._sign_enable:
+                sign_entries.append("setup")
+
+        # First added configuration is the default configuration
+        default_flag = "0"
+        if len(self.configurations.sub_nodes) == 0:
+            default_flag = "1"
+
+        conf_node = ItsNodeConfiguration(
+            conf_node_name,
+            self.configurations,
+            f"{default_flag} {', '.join(conf_desc)}",
+            opt_props=opt_props
+        )
+        if self._hash_algo:
+            ItsNodeHash(
+                "hash-1",
+                conf_node,
+                self._hash_algo
+            )
+        if self._sign_enable:
+            ItsNodeConfigurationSignature(
+                "signature-1",
+                conf_node,
+                f"{self._hash_algo},{self._sign_algo}",
+                self._sign_keyname_conf,
+                opt_props={
+                    "padding": self._pad_algo,
+                    "sign-images": sign_entries
+                }
+            )
+
+    def fitimage_emit_section_config(self):
+        if self._dtbs:
+            for dtb in self._dtbs:
+                self._fitimage_emit_one_section_config('conf-' + dtb.name.lstrip("fdt-"), dtb)
+        else:
+            self._fitimage_emit_one_section_config("conf-1")
+        self.configurations.add_property('default', self.configurations.sub_nodes[0].name)
+
+    def run_mkimage_assemble(self, itsfile, fitfile):
+        cmd = [
+            self._mkimage,
+            '-f', itsfile,
+            fitfile
+        ]
+        if self._mkimage_dtcopts:
+            cmd.insert(1, '-D')
+            cmd.insert(2, self._mkimage_dtcopts)
+        try:
+            subprocess.run(cmd, check=True, capture_output=True)
+        except subprocess.CalledProcessError as e:
+            bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}\nitsflile: {os.path.abspath(itsfile)}")
+
+    def run_mkimage_sign(self, fitfile):
+        if not self._sign_enable:
+            bb.debug(1, "FIT image signing is disabled. Skipping signing.")
+            return
+
+        # Some sanity checks because mkimage exits with 0 also without needed keys
+        sign_key_path = os.path.join(self._sign_keydir, self._sign_keyname_conf)
+        if not os.path.exists(sign_key_path + '.key') or not os.path.exists(sign_key_path + '.crt'):
+            bb.fatal("%s.key or .crt does not exist" % sign_key_path)
+        if self._sign_individual:
+            sign_key_img_path = os.path.join(self._sign_keydir, self._sign_keyname_img)
+            if not os.path.exists(sign_key_img_path + '.key') or not os.path.exists(sign_key_img_path + '.crt'):
+                bb.fatal("%s.key or .crt does not exist" % sign_key_img_path)
+
+        cmd = [
+            self._mkimage_sign,
+            '-F',
+            '-k', self._sign_keydir,
+            '-r', fitfile
+        ]
+        if self._mkimage_dtcopts:
+            cmd.extend(['-D', self._mkimage_dtcopts])
+        if self._mkimage_sign_args:
+            cmd.extend(shlex.split(self._mkimage_sign_args))
+        try:
+            subprocess.run(cmd, check=True, capture_output=True)
+        except subprocess.CalledProcessError as e:
+            bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}")
+
+
+def symlink_points_below(file_or_symlink, expected_parent_dir):
+    """returns symlink destination if it points below directory"""
+    file_path = os.path.join(expected_parent_dir, file_or_symlink)
+    if not os.path.islink(file_path):
+        return None
+
+    realpath = os.path.relpath(os.path.realpath(file_path), expected_parent_dir)
+    if realpath.startswith(".."):
+        return None
+
+    return realpath
+
+def generate_rsa_key(keydir, keyname, numbits, genrsa_args, req_args, sign_pkcs, openssl_path="openssl"):
+    sing_key_path = os.path.join(keydir, keyname)
+    if not os.path.isdir(keydir):
+        os.makedirs(keydir)
+    runCmd("%s genrsa %s -out %s.key %s" % (
+        openssl_path,
+        genrsa_args,
+        sing_key_path,
+        numbits
+    ))
+    runCmd("%s req %s %s -key %s.key -out %s.crt" % (
+        openssl_path,
+        req_args,
+        sign_pkcs,
+        sing_key_path,
+        sing_key_path
+    ))
diff --git a/meta/recipes-kernel/linux/linux-yocto-fitimage_6.12.bb b/meta/recipes-kernel/linux/linux-yocto-fitimage_6.12.bb
new file mode 100644
index 00000000000..d5efc60737e
--- /dev/null
+++ b/meta/recipes-kernel/linux/linux-yocto-fitimage_6.12.bb
@@ -0,0 +1,13 @@ 
+SUMMARY = "The Linux kernel as a FIT image (optionally with initramfs)"
+SECTION = "kernel"
+
+# If an initramfs is included in the FIT image more licenses apply.
+# But also the kernel uses more than one license (see Documentation/process/license-rules.rst)
+LICENSE = "GPL-2.0-with-Linux-syscall-note"
+LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-with-Linux-syscall-note;md5=0bad96c422c41c3a94009dcfe1bff992"
+
+LINUX_VERSION ?= "6.12.23"
+
+PV = "${LINUX_VERSION}+git"
+
+inherit kernel-fit-image