From patchwork Sat Oct 31 16:26:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102625 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.157.1793464003461657955 for ; Sat, 31 Oct 2026 09:26:43 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=IRm8mL51; spf=pass (domain: gmail.com, ip: 209.85.128.51, mailfrom: vgharaee@gmail.com) Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-4a214c5c4c0so14402405e9.3 for ; Sat, 31 Oct 2026 09:26:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464002; x=1794068802; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=0DMyeWQj4RNOUtPgy6jPZh7Vpm+V/DXd81JWMP/CxDM=; b=IRm8mL517Y/pSzduVPMUEBk6QQ8XIA0taNf8jgBTLxzbZZEManT7L3mQLyVoI5NaDj IR39Qzy6ujQQIlckhSJgfdEMgX8ywT+UYivXPZuD/aDsr60aZfcCKyrnjT2pCbAmmttS zv78wywxLJL5mUNrQIp3QXyeTMnoaZNF27uXakEjS+oJYr1k7/l3XUm9x8oDBSi6wJyY 5KCK4YfjSR1bf0cBED3pPXo/OSG262WQl20AdHolV4cLqzp+VjPwgmrT/62lagI1KoJO pfKpeCOSx5FQQW7puDeoXLwrB9a3QuHP+xpBR/9eRznzP2/FTUB4alOMr8kodGv52aem fuUQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464002; x=1794068802; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=0DMyeWQj4RNOUtPgy6jPZh7Vpm+V/DXd81JWMP/CxDM=; b=W4MzS4Q4b9aaP5hI97oc4UFUtyh/B4hPGW3e4u9rlGOxx/e/sr5vyKKRSEVBajdTkf gOIFwb/wN6qvdM6Rf6kv1zXos/AnRU5OHnTE8f9a74rGPFNiOBQVcay/mevl1kYW22Dd SVXkzUJ/66YJKd+WIO0Ndvm5itKVH9PP5D2s9SqhIB3rfjWA3QGt4aSjneZFd5UXUdll K1rc5CMwafz1TfqIWbmhkNGeh9uBOoDjQfiojQzP4K+jJ+zsI9DDQCGcPId03wzfS0Sl n98BDHjEfX4IGsWDz62fO4fg7yMiHUKP2dzPGOe8ZUcBEu2CGL7AnGNZgcEb9JrjlciW 1C2w== X-Gm-Message-State: AFq9FYKKC6t48ngSbVSZvZ7GYRjvId4fWA/ZczwDTudKdn381DGeilH1 wNHSCibNSKqN07SnAmknNspCkHdcneScZikMYOuzGTBJwo+dayvJ+xvHgL8tXg== X-Gm-Gg: AYBFou0gGSz0C0AMFQSKuo9u2H/HL9q6R5uwDTqr95xhkHBZB7pKa0prdhgP/jmzOVJ hZeChCK4E42771YcVCw+3Mdsk8QcMF6IOgr5OeKb8PAdvpBVfUCzOYEELdRDa2gm6o5vr1ZPDc9 RkLor/Am28POZd7IGleBOQ/A/jvpzyYYWKHkse1Q6Knx7h6c2xSUDg6TDKL2zYXPXrM7NnG8ANy Qk6gVq2emqGJKYrihw3ZWcU8T8sT1o64HalMS44kTaAzUiGr7y34h8curwnel6j/LhDpmzspRGe gdkKS8gv2NWIIOB/0rEf3yMUDcKKjmt1AfDZFZbET1G16/riZ7QUNPWNOnN6pOQenazx8B3BdIt 5WljEBeA41aLd1G4Lq9RgdZ/thBIzhLhWJJ/6sKfj0AGejt4TqK3/5tzkPFivdhKDck5JmGeID8 xnC1rmN+aFIxRiam0+rp9pZnJ+nvpGaDmF0v8qgPrlk+DoIjcV04gRvkAUNSHSdA0Lc1A3x4lXE zb1kEkKtiV1fWZCoCymCAZyFxWUuX2Etr2khbhSKlS1OoEXZKMyZGSJUX3bCERUX6Qt/jyy8EV4 r/N5KWKuuIJbO5PTtS2mM8DZNSvUZhGNgYWwblpXBCovRvvPuW6qEmteMvrld5rTNoDlsLb7 X-Received: by 2002:a05:600c:138c:b0:4a1:acaa:9d4c with SMTP id 5b1f17b1804b1-4ac18c740c5mr122469245e9.25.1793464001665; Sat, 31 Oct 2026 09:26:41 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.40 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:41 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 1/6] oe.fitimage: Support multiple kernel nodes and configuration selection Date: Sat, 31 Oct 2026 17:26:44 +0100 Message-ID: <20261031162649.944625-2-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:41:40 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249159 From: Vahid Gharaee Signed-off-by: Vahid Gharaee --- meta/lib/oe/fitimage.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py index 484e5c5ee0..91ccc31d46 100644 --- a/meta/lib/oe/fitimage.py +++ b/meta/lib/oe/fitimage.py @@ -193,6 +193,7 @@ class ItsNodeRootKernel(ItsNode): self._dtbs = [] self._dtb_alias = [] self._kernel = None + self._kernels = {} self._ramdisk = None self._bootscr = None self._setup = None @@ -268,8 +269,8 @@ class ItsNodeRootKernel(ItsNode): 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 kernel_id in self._kernels: + bb.fatal("Kernel section '%s' already exists in the ITS file." % kernel_id) if entrysymbol: result = subprocess.run([self._host_prefix + "nm", "vmlinux"], capture_output=True, text=True) for line in result.stdout.splitlines(): @@ -294,7 +295,10 @@ class ItsNodeRootKernel(ItsNode): compression, opt_props, ) - self._kernel = kernel_node + self._kernels[kernel_id] = kernel_node + + if self._kernel is None: + self._kernel = kernel_node def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None, dtbo_loadaddress=None, add_compatible=False): @@ -425,7 +429,7 @@ class ItsNodeRootKernel(ItsNode): self._loadables.append(loadable_node) - def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None): + def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None, kernel=None): """Emit the fitImage ITS configuration section""" opt_props = {} conf_desc = [] @@ -436,9 +440,11 @@ class ItsNodeRootKernel(ItsNode): if dtb and os.path.splitext(dtb.name)[1] == ".dtbo": include_kernel = 0 - if self._kernel and include_kernel: + selected_kernel = kernel if kernel is not None else self._kernel + + if selected_kernel and include_kernel: conf_desc.append("Linux kernel") - opt_props["kernel"] = self._kernel.name + opt_props["kernel"] = selected_kernel.name if self._sign_enable: sign_entries.append("kernel") From patchwork Sat Oct 31 16:26:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102626 Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.44]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.159.1793464005126114342 for ; Sat, 31 Oct 2026 09:26:45 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=rNAVvIZ4; spf=pass (domain: gmail.com, ip: 209.85.221.44, mailfrom: vgharaee@gmail.com) Received: by mail-wr1-f44.google.com with SMTP id ffacd0b85a97d-48afbd2c386so921352f8f.3 for ; Sat, 31 Oct 2026 09:26:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464003; x=1794068803; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=pmy/HQ2L/+JdDDbW4oH3apECpFF0tT6iV04eFC08gKE=; b=rNAVvIZ4hAQw9KPDdSrWdycm53w9u9id4Mc8b3+AZyKws2/QpquOhlv8v9ghRT44Gj UGDOmiXgVJ/Zr72d5kg3bx2SZaI/E9YcxW8P92PcFe+xl1UWI+Ikz0h7mAtFOrSUQDm7 UybLrGc3uj5EoFDY6TjHjBDa4A8ssd6fp7mrsMHBO9L3OrOQd/6spzm9t7ohxOsjbrMJ 6m3+HuYcfBN+L602ecWbS3EBj45dGoZSq8jLTvp1t0uSOsw/2chyNkUBb8tJNHpNZBWs 2XKJWzn467JISjJUf745Ij2mDEfsjxC9/nZmbBOpFQdDp3mIIljzEoV3qbRO9P5janty TDxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464003; x=1794068803; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=pmy/HQ2L/+JdDDbW4oH3apECpFF0tT6iV04eFC08gKE=; b=IwwTDhF0xHCIO7lXYz88lv3Ul3GN6Zr56jslSQapGsVI8Aqn2qk2KiqupniH7/vFGu lW616xUmIwJOtXFz+6NfxFbuNWcPFi9bnqLnHRQbZaAgxkpYpZDvXVhPtMey7hpZWH2I L/1F54HQwGR1rHH9UhFNp+wmPHmzErtNk30ar/tz2+qeC8GzV/mCprBovUJOLTQjMW1E mCQm0gm74sWPMHJ/k4LRCtPbPueps2jwI9CY4InKKTQtbkiR9hS+gtOmbyHpiZxw+9iu Q9w2azBIAyx3nFT9xPB1ATLXjIe2i/GObMIqfOGrDf0uZ42TTJg5yvSqzGLh3ZW8WLCI oXPg== X-Gm-Message-State: AFq9FYIZkPMKT7ttLo4KVPKYr3+MFqW52114ZR6M3GTM7Jx9tl48MsG5 AuGb2uGXzjEx1of2RZLJhzNckjupbTqd4bIwtUJ1DNEFn0A851/pXalmnD43sw== X-Gm-Gg: AYBFou3zHqVeNlVy2mf2/ORF9R4qe0RWtHgkfP1BUqs50s/Q85XEqK2YXzQQkpTCoEt TA6OGtGMLgkqFQhIChL2dcelM7ySKEx7WyQjbgm5+wgzP34q4A+3OAAjC24YBgc9a/bkOLh9M7u ueoRa8WIxfThfM0caC9lwIXVXK4MBZCBN4MQHmr3Hu4FFebVPjQUfOTK/wz68SA3ytfAeejyumP k7inx1+uVDsVsgHvdl1f8tUO8O41Ih+n6CmY2xqGWfwkgugCjh1nfsBcrcfgCwxYnRqLkN9lIzp NaqCcFs1PwH8Y8hFm+3K7J09cIb8WOEQqhwHYK5CFBPM0enZO8HBy2VMpkQJt/VY7F1zUIyD2iS H8+iQvgvjJPLJ8ujSBKua3CWMqTOIHjxXMtr2lJXPqXqlYbFK3vMhedFuUzNXmROYaBkrIhssR/ aAED/hb0oJTD1kSqxZNGnKDxEDzKaNb5fpRvsTNUpDCcTvmtL16lS95u5udWnfcuTdPPohCkxBe Udrz8hYv4t5qG6BCTV8cwXZb3kioPc/gA7lyfOPC2vAl5y8FRjptSlNN+Gr0zvJneb4STAeq4W7 jAShkDgdPb5fPwbWgNyQ1k6bEqu9ZsfzWwZwnABYYKFKy/HSgQMYSChkUDXDKu1+xv84M1HE X-Received: by 2002:a05:600c:8b0a:b0:4a2:e0c:2b0a with SMTP id 5b1f17b1804b1-4ac1709a200mr131502185e9.16.1793464003341; Sat, 31 Oct 2026 09:26:43 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.42 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:42 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 2/6] oe.fitimage: add metadata for multiple kernels Date: Sat, 31 Oct 2026 17:26:45 +0100 Message-ID: <20261031162649.944625-3-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:41:50 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249160 From: Vahid Gharaee Signed-off-by: Vahid Gharaee --- meta/conf/image-fitimage.conf | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/meta/conf/image-fitimage.conf b/meta/conf/image-fitimage.conf index a06d30e494..21d54ce18d 100644 --- a/meta/conf/image-fitimage.conf +++ b/meta/conf/image-fitimage.conf @@ -102,3 +102,58 @@ FIT_UBOOT_ENV ?= "" # 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 ?= "" + +# Allow user to insert additional kernel images, e.g. a second kernel built by +# another recipe or multiconfig, or a prebuilt kernel. The default kernel +# (kernel-1) from virtual/kernel is always included. Each entry is used as the +# image node name and must not collide with other image nodes, e.g. kernel-1. +# Entries can be configured through variable flags: +# FIT_KERNEL_FILENAME[id] - kernel file, relative to FIT_KERNEL_DEPLOYDIR +# (default: linux.bin, mandatory for prebuilt +# kernels) +# FIT_KERNEL_DEPLOYDIR[id] - directory containing the kernel file +# (default: ${DEPLOY_DIR_IMAGE}) +# FIT_KERNEL_RECIPE[id] - recipe whose do_deploy provides the kernel +# (default: none, the file must already exist). +# Without FIT_KERNEL_MULTICONFIG the kernel file +# must be deployed by this recipe. +# FIT_KERNEL_MULTICONFIG[id] - multiconfig building FIT_KERNEL_RECIPE[id] +# (default recipe: virtual/kernel), requires +# FIT_KERNEL_DEPLOYDIR[id] +# FIT_KERNEL_COMPRESSION[id] - default: for linux.bin deployed by +# FIT_KERNEL_RECIPE or FIT_KERNEL_MULTICONFIG the +# content of linux_comp next to it, else none +# FIT_KERNEL_LOADADDRESS[id] - default: ${UBOOT_LOADADDRESS} +# FIT_KERNEL_ENTRYPOINT[id] - default: ${UBOOT_ENTRYPOINT} +# FIT_KERNEL_TYPE[id] - default: ${UBOOT_MKIMAGE_KERNEL_TYPE} +# FIT_KERNEL_DESCRIPTION[id] - default: "Linux kernel" +# Example, a kernel from the "rt" multiconfig with the same MACHINE: +# FIT_KERNELS = "kernel-rt" +# FIT_KERNEL_MULTICONFIG[kernel-rt] = "rt" +# FIT_KERNEL_DEPLOYDIR[kernel-rt] = "${TOPDIR}/tmp-rt/deploy/images/${MACHINE}" +# Example, a second kernel recipe with KERNEL_PACKAGE_NAME = "kernel-rt": +# FIT_KERNELS = "kernel-rt" +# FIT_KERNEL_RECIPE[kernel-rt] = "linux-yocto-rt" +# FIT_KERNEL_DEPLOYDIR[kernel-rt] = "${DEPLOY_DIR_IMAGE}/kernel-rt" +# Additional kernels are only referenced by configurations from FIT_CONFIGS. +FIT_KERNELS ?= "" + +# Allow user to add configurations in addition to the ones generated per DTB +# (or conf-1 without DTBs). Each entry results in a ${FIT_CONF_PREFIX} +# configuration node which can be configured through variable flags: +# FIT_CONFIG_KERNEL[id] - kernel image node, e.g. one of FIT_KERNELS +# (default: kernel-1) +# FIT_CONFIG_DTB[id] - DTB name as in KERNEL_DEVICETREE or a file name in +# EXTERNAL_KERNEL_DEVICETREE (default: none) +# The ramdisk, u-boot script, setup and loadables are referenced by all +# configurations. +# Example: +# FIT_CONFIGS = "rt" +# FIT_CONFIG_KERNEL[rt] = "kernel-rt" +# FIT_CONFIG_DTB[rt] = "am335x-boneblack.dtb" +FIT_CONFIGS ?= "" + +# Allow user to select the default configuration by name without +# FIT_CONF_PREFIX, e.g. "rt" for "conf-rt". Takes precedence over +# FIT_CONF_DEFAULT_DTB. +FIT_CONF_DEFAULT ?= "" From patchwork Sat Oct 31 16:26:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102627 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.63.1793464006683096913 for ; Sat, 31 Oct 2026 09:26:47 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=iqq7Q1cA; spf=pass (domain: gmail.com, ip: 209.85.128.52, mailfrom: vgharaee@gmail.com) Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-4a2069b9710so14445105e9.2 for ; Sat, 31 Oct 2026 09:26:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464005; x=1794068805; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=f/FmXFq1rm9cZWCngkFdXaIU2cJTMmWOhsdYzoARAr4=; b=iqq7Q1cAJigJlmHmddqopsUlEgXMsV/AiL3cTFhcFNA+r4cUT1B4DTWQq28poMN6T4 +fMtxDbYS3vZDWiWla/oY2b/8lUet1SXcev7beZIo0fzO0rQstxTXOaaVbcMmXCK3pr3 WCLYI/h5o6MoMMeqvKOWE+tVKD7wvKoU01xqcYxqpAlGxrP2FEJX3h1q3kjfI8R7igP5 2LNjxMRRrn1QNQqqK8H5PMkWsH8rnvQpwdEuoqAzMzq2XgqpFPvgaI7Wb5L+UeMFmnL7 xyupbQz9A5H/f8DW6StlLrhbllFNiCPNcx1qXSYexXTeSTFspIqRZzrPwmh0qyJkoL6a wPBw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464005; x=1794068805; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=f/FmXFq1rm9cZWCngkFdXaIU2cJTMmWOhsdYzoARAr4=; b=halWXQC2ArIifNnO+69J7P73X502agpNaNf33dMNch26DKK/3iRZnctkocUK+7vs5U FCr2JQPq4fgUktFHgAwFZO1V9NIIAMqJn21cCpCBgjgdwULiP0Ocjy30i2ggTGoj5gbc Y3wU8Uyug1F+fT3Lv7j+BZb7N10nBRgXk23iOsKo7IXFxrs8rnlXfHhvKv5m7+Qrt7iX NQiqGcqVYgjxgIobmcbYBbpeTp3NCvPKhQaU5jADmGzAYHLEgWGNYH+HhBfZzzD52dZf 4NZLCjj4bKIonCZFj8jZ+3m/mhluJDGTb1mOf4+VgfUlFpquujv2zyMsdfMaxghwYIrM JjVg== X-Gm-Message-State: AFq9FYIuHGMAGIw7TBo641hI46WLTcCx9GR/6mFATI656Um8Z7Fifke4 hEsAwHYIOBRMIFBX9PRcptv6D6DDzzuuSONex01d6wkWDo6VYm71So34yVQCjw== X-Gm-Gg: AYBFou1SH/T03RZopCnExvjPlDQzpBZQQ3Ickr/TOm0tILB6zsRhSf2KRJAbgO6pqqw FbOce7rWSiX6YyT3dr1psIr1hINfuje55hnMAhsVWdfkkQqfmtR29o5SdJqfGvShOT792ENt/h3 90TAUrsFn+dgdIVzYjxl87BMJGWnSAsbKOjS291B4rhx8MBHciuvhQjY5Gh6/VTK5YI0WFy062A tY3zMkhWx6fkRqlAY/3Uf7U/CfElxLWgqluxAZakaFKbQ9q9elb8gXROPvtS0nc+yrZOk5vPAYm vuyzXH8Zsk51Fn7w/XX9zt2cTgmnDbgWFDDf9dG93ZZAYIj3z7Axbyt59fL0pxSW/Y+wR7Q8dH1 LTSFLER1GEnG/7X5vU6yLrqLwrrA9ITvMp4P+xuz5/qY+wMJcnkWeDN/0i4Nni9CYirL4ly7Doi MTgXcsLsSDrC04il9LTFgRsPxaGLyAY2BQW+YC8/lIQG19xbtpvMc75GH0gltE/tHmc7O6qpBZq h2Nv0PKiFbUQLPn1AHmCtkD+C6r9EyagPCvMkru7qjiPwZOIjfIG5yin6VjE1NJdLL089xtikIB shp+sTVSaRiWBhD3L2pswUCKKO4ox0ZP9s/5NPHBIFSqNhN4qfY41H9CzApm29IXECVh8n57 X-Received: by 2002:a05:600c:8116:b0:4a1:ddaa:571e with SMTP id 5b1f17b1804b1-4ac182dc29cmr121929505e9.15.1793464004955; Sat, 31 Oct 2026 09:26:44 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.44 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:44 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 3/6] oe.selftest: Add tests for multi-kernel FIT config Date: Sat, 31 Oct 2026 17:26:46 +0100 Message-ID: <20261031162649.944625-4-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:42:00 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249161 From: Vahid Gharaee Add selftests for FIT images containing multiple kernel nodes and configurations. AI-Generated: Uses GitHub copilot (Claude Opus 5.5) Signed-off-by: Vahid Gharaee --- meta/lib/oeqa/selftest/cases/fitimage.py | 156 ++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 81d2be5013..86326f40bd 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py @@ -461,6 +461,30 @@ class FitImageTestCase(OESelftestTestCase): loadables += var_loadables.split() return loadables + @staticmethod + def _get_flag_values(bb_vars, var, items): + """Map items to the per-item values of a synthetic "a ? b" flag variable""" + values = (bb_vars.get(var) or "").split("?") + return {item: values[index].strip() if index < len(values) else "" for index, item in enumerate(items)} + + @staticmethod + def _get_extra_configs(bb_vars): + """Return a list of (conf, kernel, dtb image) tuples for FIT_CONFIGS""" + configs = (bb_vars.get('FIT_CONFIGS') or "").split() + kernels = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_KERNEL', configs) + dtbs = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_DTB', configs) + _, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) + extra_configs = [] + for conf in configs: + dtb = dtbs[conf] + if dtb in dtb_symlinks: + # Symlinks of test-dtbs-as-ext point to the DTB without "-alias" + dtb = dtb.replace("-alias", "") + elif dtb: + dtb = FitImageTestCase._apply_dtbvendored(bb_vars, dtb) + extra_configs.append((conf, kernels[conf] or "kernel-1", dtb)) + return extra_configs + 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. @@ -670,11 +694,16 @@ class KernelFitImageBase(FitImageTestCase): internal_used = { 'DEPLOY_DIR_IMAGE', 'FIT_CONF_DEFAULT_DTB', + 'FIT_CONF_DEFAULT', 'FIT_CONF_MAPPINGS', 'FIT_CONF_PREFIX', + 'FIT_CONFIGS', + 'FIT_CONFIG_DTB', + 'FIT_CONFIG_KERNEL', 'FIT_DESC', 'FIT_HASH_ALG', 'FIT_KERNEL_COMP_ALG', + 'FIT_KERNELS', 'FIT_LOADABLES', 'FIT_LOADABLE_ENTRYPOINT', 'FIT_LOADABLE_LOADADDRESS', @@ -835,6 +864,8 @@ class KernelFitImageBase(FitImageTestCase): if loadables: images += loadables + images += (bb_vars.get('FIT_KERNELS') or "").split() + # configuration nodes (one per DTB, symlink, and mappings) configurations = [] if dtb_files: @@ -884,6 +915,8 @@ class KernelFitImageBase(FitImageTestCase): else: configurations = [bb_vars['FIT_CONF_PREFIX'] + '1'] + configurations += [bb_vars['FIT_CONF_PREFIX'] + conf for conf, _, _ in FitImageTestCase._get_extra_configs(bb_vars)] + # Create a list of paths for all image and configuration nodes req_its_paths = [] for image in images: @@ -926,8 +959,12 @@ class KernelFitImageBase(FitImageTestCase): if uboot_rd_entrypoint: its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint) + fit_conf_default = bb_vars.get('FIT_CONF_DEFAULT') fit_conf_default_dtb = bb_vars.get('FIT_CONF_DEFAULT_DTB') - if fit_conf_default_dtb: + if fit_conf_default: + fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-") + its_field_check.append('default = "' + fit_conf_prefix + fit_conf_default + '";') + elif fit_conf_default_dtb: fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-") its_field_check.append('default = "' + fit_conf_prefix + self._apply_dtbvendored(bb_vars, fit_conf_default_dtb) + '";') @@ -951,6 +988,12 @@ class KernelFitImageBase(FitImageTestCase): if initramfs_image and initramfs_image_bundle != "1": its_field_check.append('ramdisk = "ramdisk-1";') + # Additional configurations are emitted after the generated ones + for _, kernel, dtb in FitImageTestCase._get_extra_configs(bb_vars): + its_field_check.append('kernel = "%s";' % kernel) + if dtb: + its_field_check.append('fdt = "fdt-%s";' % dtb) + return its_field_check def _get_req_sigvalues_config(self, bb_vars): @@ -1062,6 +1105,15 @@ class KernelFitImageBase(FitImageTestCase): if loadables: req_sections[conf_name]['Loadables'] = ",".join(loadables) + # Additional kernels and configurations + for kernel in (bb_vars.get('FIT_KERNELS') or "").split(): + req_sections[kernel] = { "Type": "Kernel Image" } + for conf, kernel, dtb in FitImageTestCase._get_extra_configs(bb_vars): + conf_name = bb_vars['FIT_CONF_PREFIX'] + conf + req_sections[conf_name] = { "Kernel": kernel } + if dtb: + req_sections[conf_name]['FDT'] = 'fdt-' + dtb + # Add signing related properties if needed if uboot_sign_enable == "1": for section in req_sections: @@ -1208,6 +1260,32 @@ FIT_LOADABLE_TYPE[loadable2] = "firmware" self._gen_atf_tee_dummy_images(bb_vars) self._test_fitimage(bb_vars) + def test_fit_image_multiple_kernels(self): + """ + Summary: Check FIT_KERNELS, FIT_CONFIGS and FIT_CONF_DEFAULT + Expected: An additional kernel image node and a configuration + referring to it are present and the configuration is + the default one. + Product: oe-core + """ + machine = self._get_machine_or_skip() + config = """ +KERNEL_IMAGETYPE = "Image" +UBOOT_LOADADDRESS = "0x80080000" +UBOOT_ENTRYPOINT = "0x80080000" +# Use the linux.bin of virtual/kernel also as second kernel to avoid building another kernel +FIT_KERNELS = "kernel-2" +FIT_KERNEL_RECIPE[kernel-2] = "virtual/kernel" +FIT_CONFIGS = "second" +FIT_CONFIG_KERNEL[second] = "kernel-2" +FIT_CONF_DEFAULT = "second" +""" + config = FitImageTestCase._config_add_machine_settings(config, machine, keys=["KERNEL_DEVICETREE"]) + config = self._config_add_kernel_classes(config) + self.write_config(config) + bb_vars = self._fit_get_bb_vars() + self._test_fitimage(bb_vars) + def test_get_compatible_from_dtb(self): """Test the oe.fitimage.get_compatible_from_dtb function @@ -1501,11 +1579,14 @@ class FitImagePyTests(KernelFitImageBase): # image-fitimage.conf 'FIT_ADDRESS_CELLS': "1", 'FIT_CONF_DEFAULT_DTB': "", + 'FIT_CONF_DEFAULT': "", 'FIT_CONF_MAPPINGS': "", 'FIT_CONF_PREFIX': "conf-", + 'FIT_CONFIGS': "", 'FIT_DESC': "Kernel fitImage for a dummy distro", 'FIT_GENERATE_KEYS': "0", 'FIT_HASH_ALG': "sha256", + 'FIT_KERNELS': "", 'FIT_KEY_GENRSA_ARGS': "-F4", 'FIT_KEY_REQ_ARGS': "-batch -new", 'FIT_KEY_SIGN_PKCS': "-x509", @@ -1566,6 +1647,11 @@ class FitImagePyTests(KernelFitImageBase): bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE'), bb_vars.get("UBOOT_ENTRYSYMBOL") ) + for kernel in bb_vars['FIT_KERNELS'].split(): + root_node.fitimage_emit_section_kernel(kernel, "linux-%s.bin" % kernel, "none", + bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'), + bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE')) + dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) for dtb in dtb_files: root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb), @@ -1596,9 +1682,19 @@ class FitImagePyTests(KernelFitImageBase): "loadable-type") root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB'], bb_vars.get('FIT_CONF_MAPPINGS')) + configs = bb_vars['FIT_CONFIGS'].split() + conf_kernels = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_KERNEL', configs) + conf_dtbs = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_DTB', configs) + for conf in configs: + root_node.fitimage_emit_section_config_custom(conf, conf_kernels[conf], conf_dtbs[conf]) + if bb_vars['FIT_CONF_DEFAULT']: + root_node.fitimage_set_default_config(bb_vars['FIT_CONF_DEFAULT']) root_node.write_its_file(fitimage_its_path) self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + with open(fitimage_its_path) as its_file: + default_lines = [line for line in its_file if line.strip().startswith("default = ")] + self.assertEqual(len(default_lines), 1, "Expected exactly one default configuration: %s" % default_lines) self.logger.debug("Checking its: %s" % fitimage_its_path) self._check_its_file(bb_vars, fitimage_its_path) @@ -1665,6 +1761,64 @@ class FitImagePyTests(KernelFitImageBase): } self._test_fitimage_py(bb_vars_overrides) + def test_fitimage_py_multiple_kernels(self): + """Test FIT_KERNELS, FIT_CONFIGS and FIT_CONF_DEFAULT""" + bb_vars_overrides = { + 'KERNEL_DEVICETREE': "one.dtb two.dtb", + 'FIT_CONF_DEFAULT_DTB': "one.dtb", + 'FIT_KERNELS': "kernel-rt", + 'FIT_CONFIGS': "rt rt-two default-two", + 'FIT_CONFIG_KERNEL': "kernel-rt ? kernel-rt ? ", + 'FIT_CONFIG_DTB': " ? two.dtb ? two.dtb", + 'FIT_CONF_DEFAULT': "rt-two", + } + self._test_fitimage_py(bb_vars_overrides) + + def test_fitimage_py_multiple_kernels_unknown_kernel_error(self): + """Test that FIT_CONFIG_KERNEL referring to an unknown kernel causes a fatal error""" + bb_vars_overrides = { + 'FIT_CONFIGS': "rt", + 'FIT_CONFIG_KERNEL': "kernel-rt", + } + with self.assertRaises(BBHandledException): + self._test_fitimage_py(bb_vars_overrides) + + def test_fitimage_py_conf_default_unknown_error(self): + """Test that FIT_CONF_DEFAULT referring to an unknown configuration causes a fatal error""" + bb_vars_overrides = { + 'FIT_CONF_DEFAULT': "nonexistent", + } + with self.assertRaises(BBHandledException): + self._test_fitimage_py(bb_vars_overrides) + + def test_fitimage_py_multiple_kernels_dtb_alias(self): + """Test FIT_CONFIG_DTB referring to an external DTB symlink""" + bb_vars_overrides = { + 'PREFERRED_PROVIDER_virtual/dtb': "test-dtbs-as-ext", + 'FIT_KERNELS': "kernel-rt", + 'FIT_CONFIGS': "rt", + 'FIT_CONFIG_KERNEL': "kernel-rt", + 'FIT_CONFIG_DTB': "test-ext-alias.dtb", + } + self._test_fitimage_py(bb_vars_overrides) + + def test_fitimage_py_multiple_kernels_kernel_1_error(self): + """Test that a FIT_KERNELS entry colliding with kernel-1 causes a fatal error""" + bb_vars_overrides = { + 'FIT_KERNELS': "kernel-1", + } + with self.assertRaises(BBHandledException): + self._test_fitimage_py(bb_vars_overrides) + + def test_fitimage_py_multiple_kernels_image_collision_error(self): + """Test that a FIT_KERNELS entry colliding with another image node causes a fatal error""" + bb_vars_overrides = { + 'FIT_KERNELS': "my-loadable", + 'FIT_LOADABLES': "my-loadable", + } + with self.assertRaises(BBHandledException): + self._test_fitimage_py(bb_vars_overrides) + class UBootFitImageTests(FitImageTestCase): """Test cases for the uboot-sign bbclass""" From patchwork Sat Oct 31 16:26:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102628 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.65.1793464008331100358 for ; Sat, 31 Oct 2026 09:26:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=IBDx77sE; spf=pass (domain: gmail.com, ip: 209.85.128.53, mailfrom: vgharaee@gmail.com) Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-4a214af2076so12560395e9.0 for ; Sat, 31 Oct 2026 09:26:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464006; x=1794068806; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=Us+SRnYC9YS5WqNJJBO+fVht/yIpuhOSahNh+L/obbU=; b=IBDx77sECFmMTxrXSltD9Cx4swKSRIM8KhADlSWJPj/4tGAUo/4fdli370QIzqag96 UVvnwNK/pbYiFnpIrLS1wfunY8ahfo9fOfoDRQvYwY9RoMOsmquxbsYUkkQSaeHHJBXy /jfv4SQMTprQyQIVlmfwX6T8fuiobfymF3/KWArMeqCojfNLGjyC1eJ29WRZaDdl1i1F kPlKci3W7z7cXYxljC2K13/TXnu/2VE1dvVGIKF4RfB3231QXpY8+s25AsSucM61R0kB FfOElOyRvBQPDkEoCzlX+kMkgNxZGHoU3GihehRQromONg6IpZQ1i0nGaMiU2IOgGg4k roTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464006; x=1794068806; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=Us+SRnYC9YS5WqNJJBO+fVht/yIpuhOSahNh+L/obbU=; b=DGBSnF/MvO7AOMiXJrlCJ9TMpD5QKY9uDL6bs8MgX1hPXGQaMISmRQn1X3QIkjIqms JrA9KaVB878kyoL3KlWCsozfVSlen3W6ZSykHBm7layKbjDJxHvw86Uq7gkuVN2P/D+1 tXfPteuLN5waawsF/TSRef28luStyNztSV17jGW4pfvwNI2BurAf/8dmRaFWVtAVfEKt rq+AD2DnAMyxnK4Ai8M9dzevwnu05Mvw03poskOXYOg1Rs78vLrdM6LqGn1Hj8wHNk4T 22hvrd2W5oCdhs8RDgJ4o96MTyFuoph35u1p9twYK4Lz+uc6mDOhJxPEn1nMqo7IjwxD jMUQ== X-Gm-Message-State: AFq9FYIZZ+7wYqETX2QyOEVUxfKE2j12YWB6NF47bZFsQQ7RIX4w8Hvx kFwEHBaShOPcI83r7/AjjD1t8XCidBqtnreafa3littS/O4NW5YtYXP2x/eahg== X-Gm-Gg: AYBFou3bsgTNTV/1LaPZwZeZu1p0R8BB/XNsnLFqYgoldfL0hYcARA29U9W97qprjGU BS8K1eL1T+fpB8WQd0evguOJwFZgXvGZzh4Vi2kKDK22rTkvLkDv6nJTLJnAO2fw4n+P3ftYdF8 p+vyJcgVWicBCHTT/khuRxVhwPuC4v1fnIIU+quNF/o/WHC3tfC/iswQPNIXcIN15uhz1ZrNQ3i n5SNPmTiMI89p1XzUXYBMl1Pyp2XDvsSaDfaCwcUzqGvgxx0R5qc7oIkEoG1wwLt7Tt7LuOIcCx sGlXldtjeUfp+JtLaAqXM239CSaAMLNPM4Slfip30C9IhSLjc+qLH4IjqaYVBQMGMKBMWMZrmzE 1mBaajva2/n0dLMSjk0QcP8ZeO/J1up3/urbm1KCO/XMhbPYRtfnryjbiaXu+NLsyWbt8hzK7qC VqUHoEKr3zdwR4Fu/roeE689ZT/3yCJKWuujrlLL8LNiCXJnPebgTRZrBxZZJd1FMXwwb9tHQsM P8cwY13fiUZ2HauWA2arkLoOZi0JvC87ATyPXA3qtjFun6ZWC0mb0IWiGtjobLBi2c0uEKNWJdN jjnfMWw/x0ghOlAug05UCCSnng32TaB1iAtQUSh9x/69b8wtDSi0V7Hw4l5MSsLGj3RnY4d+ X-Received: by 2002:a05:600c:3b29:b0:4a2:167a:c939 with SMTP id 5b1f17b1804b1-4ac17d19aeamr117234485e9.21.1793464006465; Sat, 31 Oct 2026 09:26:46 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.45 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:46 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 4/6] oe.fitimage: Add custom configurations and harden multi-kernel handling Date: Sat, 31 Oct 2026 17:26:47 +0100 Message-ID: <20261031162649.944625-5-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:42:10 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249162 From: Vahid Gharaee One can now add configs that point to any kernel node and, optionally, a DTB with fitimage_emit_section_config_custom(). The default config still can be chosen by name with fitimage_set_default_config(). The default configuration is now stored and written to the ITS once, in write_its_file(). Before, fitimage_set_default_config() had to overwrite the property that fitimage_emit_section_config() had already set. DTBs and DTB symlink aliases are now looked up by the id they were added with. Before, the lookup depended on the "fdt-" node name or the alias name. Reject explicit kernel selection for FIT overlays and validate configuration generation order. Tested with oe-selftest -r fitimage.FitImagePyTests. Signed-off-by: Vahid Gharaee --- meta/classes-recipe/kernel-fit-image.bbclass | 136 ++++++++++++++++--- meta/lib/oe/fitimage.py | 85 ++++++++++-- meta/lib/oeqa/selftest/cases/fitimage.py | 37 +++++ 3 files changed, 231 insertions(+), 27 deletions(-) diff --git a/meta/classes-recipe/kernel-fit-image.bbclass b/meta/classes-recipe/kernel-fit-image.bbclass index a238de1aa2..ea4f39373d 100644 --- a/meta/classes-recipe/kernel-fit-image.bbclass +++ b/meta/classes-recipe/kernel-fit-image.bbclass @@ -29,22 +29,57 @@ python () { 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: - # Forbid direct assignment, set synthetic values for testability and - # add the vars to vardeps so their flags are part of the signature. - loadable_vars = ['FIT_LOADABLE_ARCH', 'FIT_LOADABLE_COMPRESSION', - 'FIT_LOADABLE_DESCRIPTION', 'FIT_LOADABLE_ENTRYPOINT', - 'FIT_LOADABLE_FILENAME', 'FIT_LOADABLE_LOADADDRESS', - 'FIT_LOADABLE_OS', 'FIT_LOADABLE_TYPE'] - for v in loadable_vars: - 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) - d.appendVarFlag('do_compile', 'vardeps', ' ' + ' '.join(loadable_vars)) + # Parse loadables, additional kernels and additional configurations + for items_var, flag_vars in ( + ('FIT_LOADABLES', ['FIT_LOADABLE_ARCH', 'FIT_LOADABLE_COMPRESSION', + 'FIT_LOADABLE_DESCRIPTION', 'FIT_LOADABLE_ENTRYPOINT', + 'FIT_LOADABLE_FILENAME', 'FIT_LOADABLE_LOADADDRESS', + 'FIT_LOADABLE_OS', 'FIT_LOADABLE_TYPE']), + ('FIT_KERNELS', ['FIT_KERNEL_COMPRESSION', 'FIT_KERNEL_DEPLOYDIR', + 'FIT_KERNEL_DESCRIPTION', 'FIT_KERNEL_ENTRYPOINT', + 'FIT_KERNEL_FILENAME', 'FIT_KERNEL_LOADADDRESS', + 'FIT_KERNEL_MULTICONFIG', 'FIT_KERNEL_RECIPE', + 'FIT_KERNEL_TYPE']), + ('FIT_CONFIGS', ['FIT_CONFIG_DTB', 'FIT_CONFIG_KERNEL'])): + items = d.getVar(items_var) + if items: + # Check that the configuration variables are not set directly, + # then assign them a synthetic value for testability purposes + for v in flag_vars: + if d.getVar(v): + raise bb.parse.SkipRecipe("You cannot use %s as a variable, you can only set flags." % v) + + d.appendVarFlag('do_compile', 'vardeps', ' ' + v) + + synt_value = " ? ".join([ d.getVarFlag(v, item, False) or "" for item in items.split() ]) + d.setVar(v, synt_value) + + # Preserve upstream's task signature tracking + d.appendVarFlag('do_compile', 'vardeps', ' ' + ' '.join(flag_vars)) + + # Additional kernels may be deployed by another recipe, by a recipe of + # another multiconfig or be provided as prebuilt files + kernels = (d.getVar('FIT_KERNELS') or "").split() + if "kernel-1" in kernels: + bb.fatal("FIT_KERNELS must not contain kernel-1, it is reserved for virtual/kernel") + duplicates = sorted(set(k for k in kernels if kernels.count(k) > 1)) + if duplicates: + bb.fatal("FIT_KERNELS contains duplicate entries: %s" % ' '.join(duplicates)) + for kernel in kernels: + recipe = d.getVarFlag('FIT_KERNEL_RECIPE', kernel) + mc = d.getVarFlag('FIT_KERNEL_MULTICONFIG', kernel) + if mc: + if not d.getVarFlag('FIT_KERNEL_DEPLOYDIR', kernel): + bb.fatal("FIT_KERNEL_DEPLOYDIR[%s] must be set if FIT_KERNEL_MULTICONFIG[%s] is set" % (kernel, kernel)) + d.appendVarFlag('do_compile', 'mcdepends', ' mc:%s:%s:%s:do_deploy' % + (d.getVar('BB_CURRENT_MC'), mc, recipe or 'virtual/kernel')) + elif recipe: + d.appendVarFlag('do_compile', 'depends', ' %s:do_deploy' % recipe) + else: + if not d.getVarFlag('FIT_KERNEL_FILENAME', kernel): + bb.fatal("FIT_KERNEL_FILENAME[%s] must be set if neither FIT_KERNEL_RECIPE[%s] nor FIT_KERNEL_MULTICONFIG[%s] is set" % (kernel, kernel, kernel)) + kernel_path = fit_kernel_path(d, kernel) + d.appendVarFlag('do_compile', 'file-checksums', ' %s:%s' % (kernel_path, os.path.exists(kernel_path))) } do_configure[noexec] = "1" @@ -64,6 +99,30 @@ def apply_dtbvendored(d,dtb): mapped_name = dtb.replace('/', '_') return mapped_name +def fit_kernel_path(d, kernel): + filename = d.getVarFlag('FIT_KERNEL_FILENAME', kernel) or "linux.bin" + deploydir = d.getVarFlag('FIT_KERNEL_DEPLOYDIR', kernel) or d.getVar('DEPLOY_DIR_IMAGE') + return os.path.join(deploydir, filename) + +def fit_kernel_deployed_files(d, recipe): + """Return the real paths of all files deployed by do_deploy of recipe (PN or PROVIDES) in this multiconfig""" + import bb.runqueue + mc = d.getVar('BB_CURRENT_MC') or "" + taskdepdata = d.getVar('BB_TASKDEPDATA', False) + pns = set(dep.pn for tid, dep in taskdepdata.items() + if dep.taskname == "do_deploy" and bb.runqueue.mc_from_tid(tid) == mc and + (dep.pn == recipe or recipe in dep.provides)) + if len(pns) != 1: + bb.fatal("Could not identify the do_deploy dependency providing %s (found: %s)" % (recipe, ' '.join(sorted(pns)) or "none")) + pn = pns.pop() + for arch in d.getVar('SSTATE_ARCHS').split(): + manifest = os.path.join(d.getVar('SSTATE_MANIFESTS'), "manifest-%s-%s.deploy" % (arch, pn)) + if os.path.exists(manifest): + with open(manifest) as f: + return set(os.path.realpath(line) for line in f.read().splitlines() if line and not line.endswith('/')) + bb.fatal("No deploy manifest found for %s" % pn) +fit_kernel_deployed_files[vardepsexclude] = "BB_TASKDEPDATA SSTATE_ARCHS" + python do_compile() { import shutil import oe.fitimage @@ -97,6 +156,39 @@ python do_compile() { d.getVar('UBOOT_LOADADDRESS'), d.getVar('UBOOT_ENTRYPOINT'), d.getVar('UBOOT_MKIMAGE_KERNEL_TYPE'), d.getVar("UBOOT_ENTRYSYMBOL")) + # Prepare additional kernel image sections + for kernel in d.getVar('FIT_KERNELS').split(): + recipe = d.getVarFlag('FIT_KERNEL_RECIPE', kernel) + mc = d.getVarFlag('FIT_KERNEL_MULTICONFIG', kernel) + kernel_path = fit_kernel_path(d, kernel) + if not os.path.exists(kernel_path): + bb.fatal("File for kernel %s not found at %s" % (kernel, kernel_path)) + # Artifacts of another multiconfig cannot be verified, its FIT_KERNEL_DEPLOYDIR is trusted + deployed = None + if recipe and not mc: + deployed = fit_kernel_deployed_files(d, recipe) + if os.path.realpath(kernel_path) not in deployed: + bb.fatal("File for kernel %s at %s is not deployed by %s" % (kernel, kernel_path, recipe)) + kernel_file = "linux-%s.bin" % kernel + shutil.copyfile(kernel_path, kernel_file) + + kernel_comp = d.getVarFlag('FIT_KERNEL_COMPRESSION', kernel) + if not kernel_comp: + kernel_comp = "none" + # linux_comp only describes linux.bin written by kernel-fit-extra-artifacts of the same recipe + if (recipe or mc) and os.path.basename(kernel_path) == "linux.bin": + kernel_comp_path = os.path.join(os.path.dirname(kernel_path), "linux_comp") + if not os.path.exists(kernel_comp_path) or \ + (deployed is not None and os.path.realpath(kernel_comp_path) not in deployed): + bb.fatal("%s not found or not deployed by the kernel recipe, set FIT_KERNEL_COMPRESSION[%s]" % (kernel_comp_path, kernel)) + with open(kernel_comp_path) as kernel_comp_f: + kernel_comp = kernel_comp_f.read().strip() + root_node.fitimage_emit_section_kernel(kernel, kernel_file, kernel_comp, + d.getVarFlag('FIT_KERNEL_LOADADDRESS', kernel) or d.getVar('UBOOT_LOADADDRESS'), + d.getVarFlag('FIT_KERNEL_ENTRYPOINT', kernel) or d.getVar('UBOOT_ENTRYPOINT'), + d.getVarFlag('FIT_KERNEL_TYPE', kernel) or d.getVar('UBOOT_MKIMAGE_KERNEL_TYPE'), + description=d.getVarFlag('FIT_KERNEL_DESCRIPTION', kernel) or "Linux kernel") + # Prepare a DTB image section kernel_devicetree = d.getVar('KERNEL_DEVICETREE') external_kernel_devicetree = d.getVar("EXTERNAL_KERNEL_DEVICETREE") @@ -212,6 +304,18 @@ python do_compile() { # Generate the configuration section root_node.fitimage_emit_section_config(default_dtb, d.getVar("FIT_CONF_MAPPINGS")) + # Generate additional configurations, e.g. referring to additional kernels + for conf in d.getVar('FIT_CONFIGS').split(): + conf_dtb = d.getVarFlag('FIT_CONFIG_DTB', conf) + if conf_dtb: + conf_dtb = apply_dtbvendored(d, conf_dtb) + root_node.fitimage_emit_section_config_custom(conf, + d.getVarFlag('FIT_CONFIG_KERNEL', conf), conf_dtb) + + fit_conf_default = d.getVar('FIT_CONF_DEFAULT') + if fit_conf_default: + root_node.fitimage_set_default_config(fit_conf_default) + # Write the its file root_node.write_its_file(itsfile) diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py index 91ccc31d46..55dfdc1737 100644 --- a/meta/lib/oe/fitimage.py +++ b/meta/lib/oe/fitimage.py @@ -147,8 +147,11 @@ class ItsNodeConfiguration(ItsNode): 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. + One or more kernels can be added to the FIT image along with 0 or more + device trees and 0 or 1 ramdisk. The first kernel added is the default + kernel referenced by the automatically generated configurations. + Additional configurations referring to other kernels can be added with + fitimage_emit_section_config_custom(). 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. @@ -192,7 +195,9 @@ class ItsNodeRootKernel(ItsNode): self._dtbs = [] self._dtb_alias = [] - self._kernel = None + # DTB ids as passed to fitimage_emit_section_dtb(_alias) mapped to their nodes + self._dtbs_by_id = {} + self._default_conf = None self._kernels = {} self._ramdisk = None self._bootscr = None @@ -213,11 +218,18 @@ class ItsNodeRootKernel(ItsNode): bb.fatal("FIT image signing is enabled for individual images but no image key name is provided.") def write_its_file(self, itsfile): + if self._default_conf: + self.configurations.add_property('default', self._default_conf) with open(itsfile, 'w') as f: f.write("/dts-v1/;\n\n") self.emit(f, 0) + def _check_image_id_unique(self, image_id): + if any(node.name == image_id for node in self.images.sub_nodes): + bb.fatal("Image node '%s' already exists in the ITS file." % image_id) + def its_add_node_image(self, image_id, description, image_type, compression, opt_props): + self._check_image_id_unique(image_id) image_node = ItsNodeImage( image_id, self.images, @@ -242,6 +254,7 @@ class ItsNodeRootKernel(ItsNode): return image_node def its_add_node_dtb(self, image_id, description, image_type, compression, opt_props, compatible): + self._check_image_id_unique(image_id) dtb_node = ItsNodeDtb( image_id, self.images, @@ -267,7 +280,7 @@ class ItsNodeRootKernel(ItsNode): return dtb_node def fitimage_emit_section_kernel(self, kernel_id, kernel_path, compression, - load, entrypoint, mkimage_kernel_type, entrysymbol=None): + load, entrypoint, mkimage_kernel_type, entrysymbol=None, description="Linux kernel"): """Emit the fitImage ITS kernel section""" if kernel_id in self._kernels: bb.fatal("Kernel section '%s' already exists in the ITS file." % kernel_id) @@ -290,19 +303,18 @@ class ItsNodeRootKernel(ItsNode): kernel_node = self.its_add_node_image( kernel_id, - "Linux kernel", + description, mkimage_kernel_type, compression, opt_props, ) self._kernels[kernel_id] = kernel_node - if self._kernel is None: - self._kernel = kernel_node - def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None, dtbo_loadaddress=None, add_compatible=False): """Emit the fitImage ITS DTB section""" + if dtb_id in self._dtbs_by_id: + bb.fatal("DTB '%s' already exists in the ITS file." % dtb_id) load=None dtb_ext = os.path.splitext(dtb_path)[1] if dtb_ext == ".dtbo": @@ -332,9 +344,12 @@ class ItsNodeRootKernel(ItsNode): compatible ) self._dtbs.append(dtb_node) + self._dtbs_by_id[dtb_id] = dtb_node def fitimage_emit_section_dtb_alias(self, dtb_alias_id, dtb_path, add_compatible=False): """Add a configuration node referring to another DTB""" + if dtb_alias_id in self._dtbs_by_id: + bb.fatal("DTB '%s' already exists in the ITS file." % dtb_alias_id) # Preserve the DTB's compatible string to be added to the configuration node compatible = None if add_compatible: @@ -343,6 +358,7 @@ class ItsNodeRootKernel(ItsNode): dtb_id = os.path.basename(dtb_path) dtb_alias_node = ItsNodeDtbAlias("fdt-" + dtb_id, dtb_alias_id, compatible) self._dtb_alias.append(dtb_alias_node) + self._dtbs_by_id[dtb_alias_id] = dtb_alias_node def fitimage_emit_section_boot_script(self, bootscr_id, bootscr_path): """Emit the fitImage ITS u-boot script section""" @@ -440,7 +456,7 @@ class ItsNodeRootKernel(ItsNode): if dtb and os.path.splitext(dtb.name)[1] == ".dtbo": include_kernel = 0 - selected_kernel = kernel if kernel is not None else self._kernel + selected_kernel = kernel if kernel is not None else next(iter(self._kernels.values()), None) if selected_kernel and include_kernel: conf_desc.append("Linux kernel") @@ -510,6 +526,9 @@ class ItsNodeRootKernel(ItsNode): ) def fitimage_emit_section_config(self, default_dtb_image=None, mappings=None): + if self.configurations.sub_nodes: + bb.fatal("Standard FIT configurations must be emitted before custom configurations") + if self._dtbs: dtb_extra_confs = [] dtb_confs = {} @@ -567,13 +586,57 @@ class ItsNodeRootKernel(ItsNode): ) bb.fatal("FIT configuration mapping(s) not matched with any DTB: %s" % ', '.join(unused_conf)) else: - # Currently exactly one kernel is supported. + # Without DTBs, emit one configuration for the default kernel self._fitimage_emit_one_section_config(self._conf_prefix + "1") + # The caller must emit standard configurations before custom ones. + # The first configuration is the default unless overridden by + # default_dtb_image or a later call to fitimage_set_default_config(). default_conf = self.configurations.sub_nodes[0].name if default_dtb_image and self._dtbs: default_conf = self._conf_prefix + default_dtb_image - self.configurations.add_property('default', default_conf) + self._default_conf = default_conf + + def fitimage_emit_section_config_custom(self, conf_id, kernel_id=None, dtb_id=None): + """Emit an additional configuration node for a specific kernel and optional DTB + + kernel_id is the name of a kernel image node, the default kernel is used if not set. + dtb_id is the id passed to fitimage_emit_section_dtb or fitimage_emit_section_dtb_alias. + """ + conf_node_name = self._conf_prefix + conf_id + if any(conf.name == conf_node_name for conf in self.configurations.sub_nodes): + bb.fatal("FIT configuration '%s' already exists in the ITS file." % conf_node_name) + + kernel = None + if kernel_id: + kernel = self._kernels.get(kernel_id) + if not kernel: + bb.fatal("FIT configuration '%s' refers to unknown kernel '%s'. Available kernels: %s" % + (conf_node_name, kernel_id, ' '.join(self._kernels))) + + dtb = None + if dtb_id: + dtb = self._dtbs_by_id.get(dtb_id) + if not dtb: + bb.fatal("FIT configuration '%s' refers to unknown DTB '%s'. Available DTBs: %s" % + (conf_node_name, dtb_id, ' '.join(self._dtbs_by_id))) + + if kernel_id and dtb and os.path.splitext(dtb.name)[1] == ".dtbo": + bb.fatal( + "FIT configuration '%s' cannot select kernel '%s' " + "with device tree overlay '%s'" % + (conf_node_name, kernel_id, dtb_id) + ) + + self._fitimage_emit_one_section_config(conf_node_name, dtb, kernel) + + def fitimage_set_default_config(self, conf_id): + """Select the default configuration, overriding the one chosen by fitimage_emit_section_config""" + conf_node_name = self._conf_prefix + conf_id + if not any(conf.name == conf_node_name for conf in self.configurations.sub_nodes): + bb.fatal("FIT default configuration '%s' does not exist. Available configurations: %s" % + (conf_node_name, ' '.join(conf.name for conf in self.configurations.sub_nodes))) + self._default_conf = conf_node_name def run_mkimage_assemble(self, itsfile, fitfile): cmd = [ diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 86326f40bd..2783e4e85c 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py @@ -1569,6 +1569,43 @@ KERNEL_DTBVENDORED = "1" class FitImagePyTests(KernelFitImageBase): """Test cases for the fitimage.py module without calling bitbake""" + def test_custom_config_rejects_kernel_with_overlay(self): + """An overlay configuration must not silently ignore an explicit kernel.""" + root = oe.fitimage.ItsNodeRootKernel( + "Test FIT", "1", "x86_64-poky-linux-", "x86", + "linux", "conf-", False, None, + "uboot-mkimage", "", "uboot-mkimage", "", + "sha256", "rsa2048", "pkcs-1.5", + None, False, None + ) + + root.fitimage_emit_section_kernel( + "kernel-1", "linux.bin", "none", + "0x20008000", "0x20008000", "kernel" + ) + + root.fitimage_emit_section_dtb( + "test-overlay.dtbo", "a-dir/test-overlay.dtbo" + ) + + with self.assertRaises(bb.BBHandledException): + root.fitimage_emit_section_config_custom( + "invalid", "kernel-1", "test-overlay.dtbo" + ) + + # A regular DTB may explicitly select a kernel. + root.fitimage_emit_section_dtb( + "test-board.dtb", "a-dir/test-board.dtb" + ) + + root.fitimage_emit_section_config_custom( + "regular", "kernel-1", "test-board.dtb" + ) + + # An overlay without an explicit kernel is valid. + root.fitimage_emit_section_config_custom( + "overlay", None, "test-overlay.dtbo" + ) def _test_fitimage_py(self, bb_vars_overrides=None): topdir = os.path.join(os.environ['BUILDDIR']) From patchwork Sat Oct 31 16:26:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102629 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.163.1793464009771014134 for ; Sat, 31 Oct 2026 09:26:50 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=hCDqFT+C; spf=pass (domain: gmail.com, ip: 209.85.128.47, mailfrom: vgharaee@gmail.com) Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-4a20bdaf39cso20058425e9.1 for ; Sat, 31 Oct 2026 09:26:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464008; x=1794068808; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=/BZSbL+uFoeAbLtqjpjE7qgWLM5Nq0cOjYdApkjywTc=; b=hCDqFT+CtpikrMiT+dUKTvswnm95rgLmw2Ry5Iu0yeU5wdiOAba/soq79zwK9d4McP nmF+opUu2iV9J6MP7a411geR8avPx/8ApKgKuYpF07ne6OmZyjQsLEGSJKLadS0SYF4d vAPjw0WBxJlBtuExW3/6JYvmDSIAMQu/M89bsHm6XLJ/bRNSQGwJ+D0BnnwNZT4ubI/6 iInoQE4x6jyn2cJoUwbh2Y0BoPBDJv6ZjvWFeZ0XsBrqlOwW9Qiy43iEfqwFFUMj4ICT 0XCrObLbY/2zN7aNEYYEIrdLrR6pXOrjzwx47IMPCnvS2lrNCjJYsJY2NKo8EC1Vzay9 nnDA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464008; x=1794068808; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=/BZSbL+uFoeAbLtqjpjE7qgWLM5Nq0cOjYdApkjywTc=; b=enUGp3Ci4inj5dNDDFO6gZqhgQK+fXaW4AlmG3bV+o0rlp8P7hX+fZW6yCX67xh53+ mgFMwjRX86zcn6cL0rNE6vUuKqmwW3OqiMcCXxSwrhWL2rPJaU/7TxQCuM4uegHVp7P+ X24Cxj3XVFvM2lkZONEvb6YTCwNlxAXH7asMrv3PbFaWZlujiOuUyCOa7MQndOBqB6YP RUnfSmdQl3U8rU4hbVusoHKs0O8C01IdZzRQljTyfIwT1POjjN3I21wMINieP1eiMVza u7O/ktUc3s6SJ/6ta402uko60BCoYcQlFAei/wz9x8qftlc4bRgy202UuaPf/V0/hped cgsQ== X-Gm-Message-State: AFq9FYLwqsarLLye/Hqv2isEfLOGJQomxU9152A+Vr8JCR0g+qVY4/LG oQ0sVLa3ed2FVrUI+o8WhuVVU2gRQiPo6Z9Zs7Xfp/Dcr8QnhxJzQXL/DATcIw== X-Gm-Gg: AYBFou2opKiYYw/fHUAL6i0VsGZBMlQmbo2E0DG/G7xtdDnZJz+TMKZmLUmcEVUU3nb vC1XMFfKAm8kgEbWxL1329RsiwvboziFqe7yixn7ST339w0khDr8YFxiLoLxS3Z85Cg3GzQvYn+ XbNT3ZnkxeP+/MY42mkpXAyskAyJEk7cT0+Q8bZ15JQKd1kaScM/UXcAUYqu1JQc7uoUXUY+NOY Ld6x161RCP9i5bCbS/SO/tcbJzFsipGA2yiPT+7191yp1iWsKmTm6hSfa4zHkaYU4QB10QrrH7K AWuz9vCMK1zTlTOd5ecgwGITzfWowBo8PrW5tWM8rZEOwFHHFrxKbkubP9DB8w+iW1Sb8f9BKPU yp7B8xTeHh/v+vBofZBM4gdfWFHEZDE3Cbt9XMhTmUL7XHvQNIMBOmAdXwTiiv9szX+S3xIHQiw EmHF0c0nYLQFyL7G8pD+A0a0UZ0A3Qcdj8dVjyfeXwQO6wffYCy/6gV+WLhmsnc2qQU4LBqy14F /ePl6X0YY75QBkW7S1JqdYEQQZORnjpPGf3jVhB8zdorqEa62CN/vhiITgintoufK2LGZpGVgaP 1etcpd2u2jW4cPAyG87MRMIXBnIXhKpIzwrQOzvsdlY9ut0gUBof0WO9z0TZtQga1yb2/xiP X-Received: by 2002:a05:600c:354c:b0:4a2:8c5:5e5 with SMTP id 5b1f17b1804b1-4ac17d16b1fmr127267985e9.7.1793464008059; Sat, 31 Oct 2026 09:26:48 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.47 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:47 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 5/6] oe.selftest: Improve FIT DTB alias validation Date: Sat, 31 Oct 2026 17:26:48 +0100 Message-ID: <20261031162649.944625-6-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:42:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249163 From: Vahid Gharaee Use explicit DTB alias targets instead of relying on FIT node naming conventions. Discover external DTBs and symlink aliases when generating expected FIT configurations, including vendored DTB names. Signed-off-by: Vahid Gharaee --- meta/lib/oeqa/selftest/cases/fitimage.py | 80 ++++++++++++++++-------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 2783e4e85c..6a0b0f82e9 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py @@ -432,6 +432,30 @@ class FitImageTestCase(OESelftestTestCase): mapped_name = dtb.replace('/', '_') return mapped_name + @staticmethod + def _get_test_dtb_files(): + return ( + ["test-ext.dtb", "test-overlay.dtbo"], + {"test-ext-alias.dtb": "test-ext.dtb"}, + ) + + @staticmethod + def _get_external_dtb_files(devicetree_dir): + dtbs = [] + aliases = {} + + for name in sorted(os.listdir(devicetree_dir)): + if not name.endswith((".dtb", ".dtbo")): + continue + + target = oe.fitimage.symlink_points_below(name, devicetree_dir) + if target: + aliases[name] = target + else: + dtbs.append(name) + + return dtbs, aliases + @staticmethod def _get_dtb_files(bb_vars): """Return a list of devicetree names @@ -442,15 +466,22 @@ class FitImageTestCase(OESelftestTestCase): """ kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE') all_dtbs = [] - dtb_symlinks = [] + dtb_aliases = {} + if kernel_devicetree: all_dtbs += [FitImageTestCase._apply_dtbvendored(bb_vars, dtb) for dtb in kernel_devicetree.split()] - # Support only the test recipe which provides 1 devicetree and 1 devicetree overlay - pref_prov_dtb = bb_vars.get('PREFERRED_PROVIDER_virtual/dtb') - if pref_prov_dtb == "test-dtbs-as-ext": - all_dtbs += ["test-ext.dtb", "test-overlay.dtbo"] - dtb_symlinks.append("test-ext-alias.dtb") - return (all_dtbs, dtb_symlinks) + + if bb_vars.get("PREFERRED_PROVIDER_virtual/dtb") == "test-dtbs-as-ext": + if bb_vars.get("_python_only_test"): + external_dtbs, external_aliases = FitImageTestCase._get_test_dtb_files() + else: + devicetree_dir = os.path.join(bb_vars["DEPLOY_DIR_IMAGE"], "devicetree") + external_dtbs, external_aliases = FitImageTestCase._get_external_dtb_files(devicetree_dir) + + all_dtbs.extend(external_dtbs) + dtb_aliases.update(external_aliases) + + return all_dtbs, dtb_aliases @staticmethod def _get_loadables(bb_vars): @@ -473,13 +504,12 @@ class FitImageTestCase(OESelftestTestCase): configs = (bb_vars.get('FIT_CONFIGS') or "").split() kernels = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_KERNEL', configs) dtbs = FitImageTestCase._get_flag_values(bb_vars, 'FIT_CONFIG_DTB', configs) - _, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) + _, dtb_aliases = FitImageTestCase._get_dtb_files(bb_vars) extra_configs = [] for conf in configs: dtb = dtbs[conf] - if dtb in dtb_symlinks: - # Symlinks of test-dtbs-as-ext point to the DTB without "-alias" - dtb = dtb.replace("-alias", "") + if dtb in dtb_aliases: + dtb = dtb_aliases[dtb] elif dtb: dtb = FitImageTestCase._apply_dtbvendored(bb_vars, dtb) extra_configs.append((conf, kernels[conf] or "kernel-1", dtb)) @@ -976,10 +1006,10 @@ class KernelFitImageBase(FitImageTestCase): if not dtb.endswith('.dtbo'): its_field_check.append('kernel = "kernel-1";') its_field_check.append('fdt = "fdt-%s";' % dtb) - for dtb in dtb_symlinks: + + for dtb, target in dtb_symlinks.items(): its_field_check.append('kernel = "kernel-1";') - # Works only for tests were the symlink is with -alias suffix - its_field_check.append('fdt = "fdt-%s";' % dtb.replace('-alias', '')) + its_field_check.append('fdt = "fdt-%s";' % target) if initramfs_image and initramfs_image_bundle != "1": its_field_check.append('ramdisk = "ramdisk-1";') @@ -1076,11 +1106,10 @@ class KernelFitImageBase(FitImageTestCase): req_sections[loadable]['Entry Point'] = entrypoint # Create a configuration section for each DTB if dtb_files: - for dtb in dtb_files + dtb_symlinks: + for dtb in dtb_files + list(dtb_symlinks): conf_name = bb_vars['FIT_CONF_PREFIX'] + dtb - # Assume that DTBs with an "-alias" in its name are symlink DTBs created e.g. by the - # test-dtbs-as-ext test recipe. Make the configuration node pointing to the real DTB. - real_dtb = dtb.replace("-alias", "") + # Resolve DTB aliases using their explicit target mapping. + real_dtb = dtb_symlinks.get(dtb, dtb) # dtb overlays do not refer to a kernel (yet?) if dtb.endswith('.dtbo'): req_sections[conf_name] = { @@ -1146,7 +1175,7 @@ class KernelFitImageBase(FitImageTestCase): fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg] fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg] dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) - all_dtb_names = set(dtb_files + dtb_symlinks) + all_dtb_names = set(dtb_files) | set(dtb_symlinks) # The public key is always injected into UBOOT_DTB_IMAGE (-K flag in # uboot-sign.bbclass concat_dtb). For DTB-based configurations, the # same key is also injected into each per-configuration DTB, so either @@ -1610,7 +1639,7 @@ class FitImagePyTests(KernelFitImageBase): def _test_fitimage_py(self, bb_vars_overrides=None): topdir = os.path.join(os.environ['BUILDDIR']) fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its') - + # Provide variables without calling bitbake bb_vars = { # image-fitimage.conf @@ -1660,7 +1689,10 @@ class FitImagePyTests(KernelFitImageBase): # x86 kernels produce a setup.bin section; set to the file name to # enable it, or to "" / omit the key to suppress it. 'KERNEL_SETUP_BIN': 'setup1.bin', + # ... existing entries ... + '_python_only_test': True, } + if bb_vars_overrides: bb_vars.update(bb_vars_overrides) if logging.DEBUG >= self.logger.level: @@ -1689,15 +1721,12 @@ class FitImagePyTests(KernelFitImageBase): bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'), bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE')) - dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) + dtb_files, dtb_aliases = FitImageTestCase._get_dtb_files(bb_vars) for dtb in dtb_files: root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb), bb_vars.get("UBOOT_DTB_LOADADDRESS"), bb_vars.get("UBOOT_DTBO_LOADADDRESS")) - for dtb_symlink in dtb_symlinks: - # For test purposes, assume each symlink points to a DTB with the same basename minus "-alias" - # In this case, "test-ext-alias.dtb" -> "test-ext.dtb" - dtb_target = dtb_symlink.replace("-alias", "") + for dtb_symlink, dtb_target in dtb_aliases.items(): root_node.fitimage_emit_section_dtb_alias(dtb_symlink, os.path.join("a-dir", dtb_target)) if bb_vars.get('FIT_UBOOT_ENV'): @@ -1910,6 +1939,7 @@ class UBootFitImageTests(FitImageTestCase): 'UBOOT_SIGN_IMG_KEYNAME', 'UBOOT_SIGN_KEYDIR', 'UBOOT_SIGN_KEYNAME', + 'EXTERNAL_KERNEL_DEVICETREE', } bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), UBootFitImageTests.BOOTLOADER_RECIPE) self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) From patchwork Sat Oct 31 16:26:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vahid Gharaee X-Patchwork-Id: 102630 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.69.1793464011423841747 for ; Sat, 31 Oct 2026 09:26:51 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20260707 header.b=obxfuNXu; spf=pass (domain: gmail.com, ip: 209.85.128.52, mailfrom: vgharaee@gmail.com) Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-4a215e64962so16382105e9.1 for ; Sat, 31 Oct 2026 09:26:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793464010; x=1794068810; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=LlH0iORSfvD2wu6fFSkRmEWmqtcx/phC0Of0iK+QnVI=; b=obxfuNXuBSjn0HXrqm7hLfwkfewZ3V46x6kAY6voOzNaW6foRJUSJzLLq/wFOd6a6h Htb0TzqrsRGIzmYbnsZtwdAMFmtmR1jao9ukGCgyRsIz1mwrPTKLlYI2tMxbyctWS+d1 YHG+uzWge8sYr+uBSz8zj0VxAMFA3o+VBa9LrQ7z/ony4TrKsgMc33scUqd4EaG8LOq1 5pDBT/WhSyw9wKGIQuyQdNAOe5FrYTkW8iVsCdVRyqbEhpPbEFU19JJC+egTq1evUKPw bcHg+XnMY+huqwolMl6LaptFCD6gTwnwc+NH9B1MujGBJC5HsqULXV7Oykqo5xL21PDB mZvw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793464010; x=1794068810; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=LlH0iORSfvD2wu6fFSkRmEWmqtcx/phC0Of0iK+QnVI=; b=zDhIxEFU7ka71hLzv1ciDjvsD4ZuyBqt9lWuEd1I57JGVP+jNS5IJ5o7LPizwRexJV U1+563Hz55S1ZfJ/btxgfcQFb+T8sozxFoUSXoF6EMByTFgv87WzEyIAFAbU++eeoQbU LJ8xhGs/VeI9KvLe8zymIxRHOPRzf+o56WVI99/byxCetqva541hpuSN5UKecA4VrNQP Xh1oFGpA6nDXjiCyhWTRt2hCDE8Y1mz5nsjufUpkswqnSV72FCzrs5YocwVW9YGUTZtI hBoiGKekpa/XRkvT6ZLLIG/pIDGeXIJ7ca31md3snTP2Z8GWhGYt6EdIk3Rhsx9TGwuA WWdg== X-Gm-Message-State: AFq9FYJG4NofPVIeuwM3/NAyVZThgQwjdzS5y2NwMC+en528lAERghHL NM9M6ZW5G1PIibznfpdRISOeMhOXoIpJRjdbcoJVdFOfQsubGfvs0Esl69QtqA== X-Gm-Gg: AYBFou3Rmu1TKOEsQvbONjcBhA7OPmG1PtaOvATK1oo/+ghQ+3WZgZ7Z9oN9rwZ+QuC c0euRhsWpR7P07EMGHBC2js9SFLhzTVwucl9q7vc79gO/AMXRkhrSKM5xioiCa2q7Dkewts37Nk CDFs/raHIWWVXxp8By6dvQp+w9V/+hMwksWLaem6dlNcDfM2t2zf50tRHwLxlB3RwyomGvGAl6j q3g+9/B8FKaz+g5vQh2UWLjw71h1S28Wm5bWWiCmkJInJc92fgya6ktikyil4HkzNh012NkVEtd 40yOtAspuxw2ReIOJzR0gIVfnV6vYAG0bQcD6V97D+IuUlvmVw1sqt2h3v5hOeMds0f/K9c5s34 342JIC/56KSo2w8izXfhM5l5f5NXyWTC2bx3dyt6JQ3AQ98JTtUeKdn30Tjf4UOVGge7D+dvNJ7 mjnopCGgbotawdWZhqubl+LEYcN3IQo+aT3tarY9tcoqmTefxPzaqJMZozu8uL8cYCId2bg/oOx pAvunc+psMSOfbxgudAFsvzLICm8eZjQufcAXxOHvA1jvZq/d0jdBWbIkedtT0JvRn1mBthOZy2 p0j5qNplPjGE5vbRoxoRxseu+k4VVuh9LneV+n0joc3/12FoaP0Gu1C8/6vEzAfr9kPiZEO9Rum 1xGoEycImGy26jevVPA== X-Received: by 2002:a05:600c:8b5c:b0:4a1:fe55:88ef with SMTP id 5b1f17b1804b1-4ac1889c4b9mr135140865e9.20.1793464009629; Sat, 31 Oct 2026 09:26:49 -0700 (PDT) Return-Path: Received: from vahid-mr.fritz.box ([2001:16e0:257:b301:9825:3e45:cd0a:824b]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa813d2640sm214214455e9.6.2026.10.31.09.26.49 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Oct 2026 09:26:49 -0700 (PDT) From: "Vahid Gharaee" To: openembedded-core@lists.openembedded.org Subject: [PATCH v3 6/6] kernel-fit-image: preserve vendored DTB aliases for external overrides Date: Sat, 31 Oct 2026 17:26:49 +0100 Message-ID: <20261031162649.944625-7-vgharaee@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261031162649.944625-1-vgharaee@gmail.com> References: <20261031162649.944625-1-vgharaee@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Sun, 01 Nov 2026 18:42:30 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249164 From: Vahid Gharaee When an external DTB overrides a kernel-provided DTB, the FIT generation skips the kernel DTB. With KERNEL_DTBVENDORED enabled, this also drops the vendored configuration name. Preserve the vendored name by creating a configuration alias referencing the external DTB image. Resolve external symlinks to their actual DTB targets. Add regression tests for regular and symlinked external DTB overrides. Signed-off-by: Vahid Gharaee --- .../recipes-test/ext-dtb/test-dtbs-as-ext.bb | 11 ++ meta/classes-recipe/kernel-fit-image.bbclass | 7 ++ meta/lib/oeqa/selftest/cases/fitimage.py | 105 ++++++++++++++++-- 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/meta-selftest/recipes-test/ext-dtb/test-dtbs-as-ext.bb b/meta-selftest/recipes-test/ext-dtb/test-dtbs-as-ext.bb index 217ca36235..de994cebce 100644 --- a/meta-selftest/recipes-test/ext-dtb/test-dtbs-as-ext.bb +++ b/meta-selftest/recipes-test/ext-dtb/test-dtbs-as-ext.bb @@ -8,6 +8,7 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda inherit devicetree +TEST_EXTRA_DTB ?= "" COMPATIBLE_MACHINE = ".*" SRC_URI = "\ @@ -18,8 +19,18 @@ SRC_URI = "\ # Sym-links are handled as extra configuration nodes in FIT images. do_install:append() { ln -sf test-ext.dtb "${D}/boot/devicetree/test-ext-alias.dtb" + + if [ -n "${TEST_EXTRA_DTB}" ]; then + cp "${D}/boot/devicetree/test-ext.dtb" \ + "${D}/boot/devicetree/${TEST_EXTRA_DTB}" + fi } do_deploy:append() { ln -sf test-ext.dtb "${DEPLOYDIR}/devicetree/test-ext-alias.dtb" + + if [ -n "${TEST_EXTRA_DTB}" ]; then + cp "${DEPLOYDIR}/devicetree/test-ext.dtb" \ + "${DEPLOYDIR}/devicetree/${TEST_EXTRA_DTB}" + fi } diff --git a/meta/classes-recipe/kernel-fit-image.bbclass b/meta/classes-recipe/kernel-fit-image.bbclass index ea4f39373d..1ef2717908 100644 --- a/meta/classes-recipe/kernel-fit-image.bbclass +++ b/meta/classes-recipe/kernel-fit-image.bbclass @@ -192,6 +192,7 @@ python do_compile() { # Prepare a DTB image section kernel_devicetree = d.getVar('KERNEL_DEVICETREE') external_kernel_devicetree = d.getVar("EXTERNAL_KERNEL_DEVICETREE") + external_dtb_aliases = {} if kernel_devicetree: for dtb in kernel_devicetree.split(): # In deploy_dir the DTBs are without sub-directories also with KERNEL_DTBVENDORED = "1" @@ -205,6 +206,8 @@ python do_compile() { if external_kernel_devicetree: ext_dtb_path = os.path.join(external_kernel_devicetree, dtb_name) if os.path.exists(ext_dtb_path) and os.path.getsize(ext_dtb_path) > 0: + if conf_name != dtb_name: + external_dtb_aliases[conf_name] = ext_dtb_path continue # Copy the dtb or dtbo file into the FIT image assembly directory @@ -229,6 +232,10 @@ python do_compile() { root_node.fitimage_emit_section_dtb(dtb_name, dtb_name, d.getVar("UBOOT_DTB_LOADADDRESS"), d.getVar("UBOOT_DTBO_LOADADDRESS"), True) + for alias, dtb_path in external_dtb_aliases.items(): + symlink_target = oe.fitimage.symlink_points_below(os.path.basename(dtb_path), external_kernel_devicetree) + root_node.fitimage_emit_section_dtb_alias(alias, symlink_target or dtb_path) + # Prepare a u-boot script section fit_uboot_env = d.getVar("FIT_UBOOT_ENV") if fit_uboot_env: diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 6a0b0f82e9..90b7a20ad0 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py @@ -467,10 +467,9 @@ class FitImageTestCase(OESelftestTestCase): kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE') all_dtbs = [] dtb_aliases = {} + external_aliases = {} + kernel_dtbs = kernel_devicetree.split() if kernel_devicetree else [] - if kernel_devicetree: - all_dtbs += [FitImageTestCase._apply_dtbvendored(bb_vars, dtb) for dtb in kernel_devicetree.split()] - if bb_vars.get("PREFERRED_PROVIDER_virtual/dtb") == "test-dtbs-as-ext": if bb_vars.get("_python_only_test"): external_dtbs, external_aliases = FitImageTestCase._get_test_dtb_files() @@ -481,6 +480,23 @@ class FitImageTestCase(OESelftestTestCase): all_dtbs.extend(external_dtbs) dtb_aliases.update(external_aliases) + for dtb in kernel_dtbs: + dtb_name = os.path.basename(dtb) + mapped_name = FitImageTestCase._apply_dtbvendored(bb_vars, dtb) + + if dtb_name in all_dtbs: + target = dtb_name + elif dtb_name in external_aliases: + target = external_aliases[dtb_name] + else: + target = None + + if target: + if mapped_name != target: + dtb_aliases[mapped_name] = target + else: + all_dtbs.append(mapped_name) + return all_dtbs, dtb_aliases @staticmethod @@ -508,10 +524,10 @@ class FitImageTestCase(OESelftestTestCase): extra_configs = [] for conf in configs: dtb = dtbs[conf] - if dtb in dtb_aliases: - dtb = dtb_aliases[dtb] - elif dtb: + if dtb: dtb = FitImageTestCase._apply_dtbvendored(bb_vars, dtb) + dtb = dtb_aliases.get(dtb, dtb) + extra_configs.append((conf, kernels[conf] or "kernel-1", dtb)) return extra_configs @@ -1379,6 +1395,58 @@ PREFERRED_PROVIDER_virtual/dtb = "test-dtbs-as-ext" self._gen_atf_tee_dummy_images(bb_vars) self._test_fitimage(bb_vars) + def test_fit_image_ext_dtb_vendored_override(self): + """Check that an external DTB overrides a vendored kernel DTB.""" + machine = self._get_machine_or_skip() + + if machine != "qemuarm64": + self.skipTest("This test requires the qemuarm64 foundation-v8.dtb") + + config = """ +KERNEL_DTBVENDORED = "1" +PREFERRED_PROVIDER_virtual/dtb = "test-dtbs-as-ext" +TEST_EXTRA_DTB = "foundation-v8.dtb" +FIT_CONF_DEFAULT_DTB = "arm/foundation-v8.dtb" +FIT_CONFIGS = "external-override" +FIT_CONFIG_DTB[external-override] = "arm/foundation-v8.dtb" +""" + config = self._config_add_machine_settings(config, machine, keys=["KERNEL_DEVICETREE"]) + config = self._config_add_kernel_classes(config) + config = self._config_add_uboot_env(config) + + self.write_config(config) + + bb_vars = self._fit_get_bb_vars() + self._gen_atf_tee_dummy_images(bb_vars) + + its_path, fit_path = self._bitbake_fit_image(bb_vars) + + self.assertExists(its_path) + self.assertExists(fit_path) + + self.logger.info("Regression test ITS: %s", its_path) + + with open(its_path, encoding="utf-8") as f: + its = f.read() + + self.assertIn("fdt-foundation-v8.dtb", its) + self.assertIn("conf-arm_foundation-v8.dtb", its) + self.assertIn("conf-external-override", its) + self.assertNotIn("fdt-arm_foundation-v8.dtb {", its) + # The vendored configuration must be the default. + self.assertIn('default = "conf-arm_foundation-v8.dtb";', its) + + # Both the vendored and custom configurations must reference + # the external DTB image. + for conf in ("conf-arm_foundation-v8.dtb", "conf-external-override"): + match = re.search(r'\b' + re.escape(conf) + r'\s*\{([^{}]*)', its) + self.assertIsNotNone(match, "Missing configuration: %s" % conf) + self.assertIn('fdt = "fdt-foundation-v8.dtb";', match.group(1)) + + self._check_its_file(bb_vars, its_path) + + uboot_tools_bindir = self._setup_native('u-boot-tools-native') + self._check_fitimage(bb_vars, fit_path, uboot_tools_bindir) def test_sign_fit_image_configurations(self): """ @@ -1616,17 +1684,17 @@ class FitImagePyTests(KernelFitImageBase): root.fitimage_emit_section_dtb( "test-overlay.dtbo", "a-dir/test-overlay.dtbo" ) - + with self.assertRaises(bb.BBHandledException): root.fitimage_emit_section_config_custom( "invalid", "kernel-1", "test-overlay.dtbo" ) - + # A regular DTB may explicitly select a kernel. root.fitimage_emit_section_dtb( "test-board.dtb", "a-dir/test-board.dtb" ) - + root.fitimage_emit_section_config_custom( "regular", "kernel-1", "test-board.dtb" ) @@ -1639,7 +1707,7 @@ class FitImagePyTests(KernelFitImageBase): def _test_fitimage_py(self, bb_vars_overrides=None): topdir = os.path.join(os.environ['BUILDDIR']) fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its') - + # Provide variables without calling bitbake bb_vars = { # image-fitimage.conf @@ -1764,6 +1832,23 @@ class FitImagePyTests(KernelFitImageBase): self.logger.debug("Checking its: %s" % fitimage_its_path) self._check_its_file(bb_vars, fitimage_its_path) + def test_fitimage_py_vendored_external_dtb_symlink_override(self): + bb_vars = { + "KERNEL_DEVICETREE": "arm/test-ext-alias.dtb", + "KERNEL_DTBVENDORED": "1", + "PREFERRED_PROVIDER_virtual/dtb": "test-dtbs-as-ext", + "_python_only_test": True, + } + + dtbs, aliases = FitImageTestCase._get_dtb_files(bb_vars) + + self.assertIn("test-ext.dtb", dtbs) + self.assertNotIn("arm_test-ext-alias.dtb", dtbs) + self.assertEqual(aliases["arm_test-ext-alias.dtb"],"test-ext.dtb") + + # Verify that the original external symlink alias is preserved + self.assertEqual(aliases["test-ext-alias.dtb"], "test-ext.dtb") + def test_fitimage_py_default(self): self._test_fitimage_py()