summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrian Freihofer <adrian.freihofer@siemens.com>2025-06-03 10:23:22 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2025-06-05 11:02:21 +0100
commit36bea94fe7d01b572f6802683dba8b8e84f426e8 (patch)
tree9aaff25df8d81be9cf7bc1cf81e43477e0f555c1
parentceee2575537cfeabb99f76057416701d6cf0fc8b (diff)
downloadpoky-36bea94fe7d01b572f6802683dba8b8e84f426e8.tar.gz
kernel-fit-image.bbclass: add a new FIT image implementation
The new recipe linux-yocto-fitimage.bb and the new kernel-fit-image.bbclass are intended to become successors of the kernel-fitimage.bbclass. 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 an 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 recipe takes the kernel artifacts from the deploy folder. There was also a test implementation passing the kernel artifacts via sysroot directory. This requires changes on the kernel.bbclass to make it copying the artifacts also to the sysroot directory while the same artifacts are already in the sstate-cached deploy directory. The new class kernel-fit-extra-artifacts.bbclass generates and deploys the kernel binary intended for inclusion in a FIT image. Note that the kernel used in a FIT image is a stripped (and optionally compressed) vmlinux ELF binary - not a self-extracting format like zImage, which is already available in the deploy directory if needed separately. The kernel-fit-extra-artifacts.bbclass can be used like this: KERNEL_CLASSES += "kernel-fit-extra-artifacts" (if uImage support is not needed, or with :append otherwise) The long story about this issue is here: [YOCTO #12912] (From OE-Core rev: 05d0c7342d7638dbe8a9f2fd3d1c709ee87d6579) Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/classes-recipe/kernel-fit-extra-artifacts.bbclass19
-rw-r--r--meta/classes-recipe/kernel-fit-image.bbclass187
-rw-r--r--meta/classes/multilib.bbclass1
-rw-r--r--meta/lib/oe/fitimage.py547
-rw-r--r--meta/recipes-kernel/linux/linux-yocto-fitimage.bb13
5 files changed, 767 insertions, 0 deletions
diff --git a/meta/classes-recipe/kernel-fit-extra-artifacts.bbclass b/meta/classes-recipe/kernel-fit-extra-artifacts.bbclass
new file mode 100644
index 0000000000..385fe9895a
--- /dev/null
+++ b/meta/classes-recipe/kernel-fit-extra-artifacts.bbclass
@@ -0,0 +1,19 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7# Generate and deploy additional artifacts required for FIT image creation.
8# To use this class, add it to the KERNEL_CLASSES variable.
9
10inherit kernel-uboot
11
12kernel_do_deploy:append() {
13 # Provide the kernel artifacts to post processing recipes e.g. for creating a FIT image
14 uboot_prep_kimage "$deployDir"
15 # For x86 a setup.bin needs to be include"d in a fitImage as well
16 if [ -e ${KERNEL_OUTPUT_DIR}/setup.bin ]; then
17 install -D "${B}/${KERNEL_OUTPUT_DIR}/setup.bin" "$deployDir/"
18 fi
19}
diff --git a/meta/classes-recipe/kernel-fit-image.bbclass b/meta/classes-recipe/kernel-fit-image.bbclass
new file mode 100644
index 0000000000..6d80cd4bb4
--- /dev/null
+++ b/meta/classes-recipe/kernel-fit-image.bbclass
@@ -0,0 +1,187 @@
1
2inherit kernel-arch kernel-artifact-names uboot-config deploy
3require conf/image-fitimage.conf
4
5S = "${WORKDIR}/sources"
6UNPACKDIR = "${S}"
7
8PACKAGE_ARCH = "${MACHINE_ARCH}"
9
10DEPENDS += "\
11 u-boot-tools-native dtc-native \
12 ${@'kernel-signing-keys-native' if d.getVar('FIT_GENERATE_KEYS') == '1' else ''} \
13"
14
15python () {
16 image = d.getVar('INITRAMFS_IMAGE')
17 if image and d.getVar('INITRAMFS_IMAGE_BUNDLE') != '1':
18 if d.getVar('INITRAMFS_MULTICONFIG'):
19 mc = d.getVar('BB_CURRENT_MC')
20 d.appendVarFlag('do_compile', 'mcdepends', ' mc:' + mc + ':${INITRAMFS_MULTICONFIG}:${INITRAMFS_IMAGE}:do_image_complete')
21 else:
22 d.appendVarFlag('do_compile', 'depends', ' ${INITRAMFS_IMAGE}:do_image_complete')
23
24 #check if there are any dtb providers
25 providerdtb = d.getVar("PREFERRED_PROVIDER_virtual/dtb")
26 if providerdtb:
27 d.appendVarFlag('do_compile', 'depends', ' virtual/dtb:do_populate_sysroot')
28 d.setVar('EXTERNAL_KERNEL_DEVICETREE', "${RECIPE_SYSROOT}/boot/devicetree")
29}
30
31do_configure[noexec] = "1"
32
33UBOOT_MKIMAGE_KERNEL_TYPE ?= "kernel"
34KERNEL_IMAGEDEST ?= "/boot"
35
36python do_compile() {
37 import shutil
38 import oe.fitimage
39
40 itsfile = "fit-image.its"
41 fitname = "fitImage"
42 kernel_deploydir = d.getVar('DEPLOY_DIR_IMAGE')
43 kernel_deploysubdir = d.getVar('KERNEL_DEPLOYSUBDIR')
44 if kernel_deploysubdir:
45 kernel_deploydir = os.path.join(kernel_deploydir, kernel_deploysubdir)
46
47 # Collect all the its nodes before the its file is generated and mkimage gets executed
48 root_node = oe.fitimage.ItsNodeRootKernel(
49 d.getVar("FIT_DESC"), d.getVar("FIT_ADDRESS_CELLS"),
50 d.getVar('HOST_PREFIX'), d.getVar('UBOOT_ARCH'), d.getVar("FIT_CONF_PREFIX"),
51 oe.types.boolean(d.getVar('UBOOT_SIGN_ENABLE')), d.getVar("UBOOT_SIGN_KEYDIR"),
52 d.getVar("UBOOT_MKIMAGE"), d.getVar("UBOOT_MKIMAGE_DTCOPTS"),
53 d.getVar("UBOOT_MKIMAGE_SIGN"), d.getVar("UBOOT_MKIMAGE_SIGN_ARGS"),
54 d.getVar('FIT_HASH_ALG'), d.getVar('FIT_SIGN_ALG'), d.getVar('FIT_PAD_ALG'),
55 d.getVar('UBOOT_SIGN_KEYNAME'),
56 oe.types.boolean(d.getVar('FIT_SIGN_INDIVIDUAL')), d.getVar('UBOOT_SIGN_IMG_KEYNAME')
57 )
58
59 # Prepare a kernel image section.
60 shutil.copyfile(os.path.join(kernel_deploydir, "linux.bin"), "linux.bin")
61 with open(os.path.join(kernel_deploydir, "linux_comp")) as linux_comp_f:
62 linux_comp = linux_comp_f.read()
63 root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", linux_comp,
64 d.getVar('UBOOT_LOADADDRESS'), d.getVar('UBOOT_ENTRYPOINT'),
65 d.getVar('UBOOT_MKIMAGE_KERNEL_TYPE'), d.getVar("UBOOT_ENTRYSYMBOL"))
66
67 # Prepare a DTB image section
68 kernel_devicetree = d.getVar('KERNEL_DEVICETREE')
69 external_kernel_devicetree = d.getVar("EXTERNAL_KERNEL_DEVICETREE")
70 if kernel_devicetree:
71 for dtb in kernel_devicetree.split():
72 # In deploy_dir the DTBs are without sub-directories also with KERNEL_DTBVENDORED = "1"
73 dtb_name = os.path.basename(dtb)
74
75 # Skip DTB if it's also provided in EXTERNAL_KERNEL_DEVICETREE directory
76 if external_kernel_devicetree:
77 ext_dtb_path = os.path.join(external_kernel_devicetree, dtb_name)
78 if os.path.exists(ext_dtb_path) and os.path.getsize(ext_dtb_path) > 0:
79 continue
80
81 # Copy the dtb or dtbo file into the FIT image assembly directory
82 shutil.copyfile(os.path.join(kernel_deploydir, dtb_name), dtb_name)
83 root_node.fitimage_emit_section_dtb(dtb_name, dtb_name,
84 d.getVar("UBOOT_DTB_LOADADDRESS"), d.getVar("UBOOT_DTBO_LOADADDRESS"))
85
86 if external_kernel_devicetree:
87 # iterate over all .dtb and .dtbo files in the external kernel devicetree directory
88 # and copy them to the FIT image assembly directory
89 for dtb_name in sorted(os.listdir(external_kernel_devicetree)):
90 if dtb_name.endswith('.dtb') or dtb_name.endswith('.dtbo'):
91 dtb_path = os.path.join(external_kernel_devicetree, dtb_name)
92
93 # For symlinks, add a configuration node that refers to the DTB image node to which the symlink points
94 symlink_target = oe.fitimage.symlink_points_below(dtb_name, external_kernel_devicetree)
95 if symlink_target:
96 root_node.fitimage_emit_section_dtb_alias(dtb_name, symlink_target, True)
97 # For real DTB files add an image node and a configuration node
98 else:
99 shutil.copyfile(dtb_path, dtb_name)
100 root_node.fitimage_emit_section_dtb(dtb_name, dtb_name,
101 d.getVar("UBOOT_DTB_LOADADDRESS"), d.getVar("UBOOT_DTBO_LOADADDRESS"), True)
102
103 # Prepare a u-boot script section
104 fit_uboot_env = d.getVar("FIT_UBOOT_ENV")
105 if fit_uboot_env:
106 root_node.fitimage_emit_section_boot_script("bootscr-"+fit_uboot_env , fit_uboot_env)
107
108 # Prepare a setup section (For x86)
109 setup_bin_path = os.path.join(kernel_deploydir, "setup.bin")
110 if os.path.exists(setup_bin_path):
111 shutil.copyfile(setup_bin_path, "setup.bin")
112 root_node.fitimage_emit_section_setup("setup-1", "setup.bin")
113
114 # Prepare a ramdisk section.
115 initramfs_image = d.getVar('INITRAMFS_IMAGE')
116 if initramfs_image and d.getVar("INITRAMFS_IMAGE_BUNDLE") != '1':
117 # Find and use the first initramfs image archive type we find
118 found = False
119 for img in d.getVar("FIT_SUPPORTED_INITRAMFS_FSTYPES").split():
120 initramfs_path = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), "%s.%s" % (d.getVar('INITRAMFS_IMAGE_NAME'), img))
121 if os.path.exists(initramfs_path):
122 bb.note("Found initramfs image: " + initramfs_path)
123 found = True
124 root_node.fitimage_emit_section_ramdisk("ramdisk-1", initramfs_path,
125 initramfs_image,
126 d.getVar("UBOOT_RD_LOADADDRESS"),
127 d.getVar("UBOOT_RD_ENTRYPOINT"))
128 break
129 else:
130 bb.note("Did not find initramfs image: " + initramfs_path)
131
132 if not found:
133 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')))
134
135 # Generate the configuration section
136 root_node.fitimage_emit_section_config(d.getVar("FIT_CONF_DEFAULT_DTB"))
137
138 # Write the its file
139 root_node.write_its_file(itsfile)
140
141 # Assemble the FIT image
142 root_node.run_mkimage_assemble(itsfile, fitname)
143
144 # Sign the FIT image if required
145 root_node.run_mkimage_sign(fitname)
146}
147do_compile[depends] += "virtual/kernel:do_deploy"
148
149do_install() {
150 install -d "${D}/${KERNEL_IMAGEDEST}"
151 install -m 0644 "${B}/fitImage" "${D}/${KERNEL_IMAGEDEST}/fitImage"
152}
153
154FILES:${PN} = "${KERNEL_IMAGEDEST}"
155
156
157do_deploy() {
158 deploy_dir="${DEPLOYDIR}"
159 if [ -n "${KERNEL_DEPLOYSUBDIR}" ]; then
160 deploy_dir="${DEPLOYDIR}/${KERNEL_DEPLOYSUBDIR}"
161 fi
162 install -d "$deploy_dir"
163 install -m 0644 "${B}/fitImage" "$deploy_dir/fitImage"
164 install -m 0644 "${B}/fit-image.its" "$deploy_dir/fit-image.its"
165
166 if [ "${INITRAMFS_IMAGE_BUNDLE}" != "1" ]; then
167 ln -snf fit-image.its "$deploy_dir/fitImage-its-${KERNEL_FIT_NAME}.its"
168 if [ -n "${KERNEL_FIT_LINK_NAME}" ] ; then
169 ln -snf fit-image.its "$deploy_dir/fitImage-its-${KERNEL_FIT_LINK_NAME}"
170 fi
171 fi
172
173 if [ -n "${INITRAMFS_IMAGE}" ]; then
174 ln -snf fit-image-its "$deploy_dir/fitImage-its-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_NAME}.its"
175 if [ -n "${KERNEL_FIT_LINK_NAME}" ]; then
176 ln -snf fit-image.its "$deploy_dir/fitImage-its-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_LINK_NAME}"
177 fi
178
179 if [ "${INITRAMFS_IMAGE_BUNDLE}" != "1" ]; then
180 ln -snf fitImage "$deploy_dir/fitImage-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_NAME}${KERNEL_FIT_BIN_EXT}"
181 if [ -n "${KERNEL_FIT_LINK_NAME}" ] ; then
182 ln -snf fitImage "$deploy_dir/fitImage-${INITRAMFS_IMAGE_NAME}-${KERNEL_FIT_LINK_NAME}"
183 fi
184 fi
185 fi
186}
187addtask deploy after do_compile before do_build
diff --git a/meta/classes/multilib.bbclass b/meta/classes/multilib.bbclass
index a4151658a6..b959bbd93c 100644
--- a/meta/classes/multilib.bbclass
+++ b/meta/classes/multilib.bbclass
@@ -21,6 +21,7 @@ python multilib_virtclass_handler () {
21 bpn = d.getVar("BPN") 21 bpn = d.getVar("BPN")
22 if ("virtual/kernel" in provides 22 if ("virtual/kernel" in provides
23 or bb.data.inherits_class('module-base', d) 23 or bb.data.inherits_class('module-base', d)
24 or bb.data.inherits_class('kernel-fit-image', d)
24 or bpn in non_ml_recipes): 25 or bpn in non_ml_recipes):
25 raise bb.parse.SkipRecipe("We shouldn't have multilib variants for %s" % bpn) 26 raise bb.parse.SkipRecipe("We shouldn't have multilib variants for %s" % bpn)
26 27
diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py
new file mode 100644
index 0000000000..f303799155
--- /dev/null
+++ b/meta/lib/oe/fitimage.py
@@ -0,0 +1,547 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# This file contains common functions for the fitimage generation
7
8import os
9import shlex
10import subprocess
11import bb
12
13from oeqa.utils.commands import runCmd
14
15class ItsNode:
16 INDENT_SIZE = 8
17
18 def __init__(self, name, parent_node, sub_nodes=None, properties=None):
19 self.name = name
20 self.parent_node = parent_node
21
22 self.sub_nodes = []
23 if sub_nodes:
24 self.sub_nodes = sub_nodes
25
26 self.properties = {}
27 if properties:
28 self.properties = properties
29
30 if parent_node:
31 parent_node.add_sub_node(self)
32
33 def add_sub_node(self, sub_node):
34 self.sub_nodes.append(sub_node)
35
36 def add_property(self, key, value):
37 self.properties[key] = value
38
39 def emit(self, f, indent):
40 indent_str_name = " " * indent
41 indent_str_props = " " * (indent + self.INDENT_SIZE)
42 f.write("%s%s {\n" % (indent_str_name, self.name))
43 for key, value in self.properties.items():
44 bb.debug(1, "key: %s, value: %s" % (key, str(value)))
45 # Single integer: <0x12ab>
46 if isinstance(value, int):
47 f.write(indent_str_props + key + ' = <0x%x>;\n' % value)
48 # list of strings: "string1", "string2" or integers: <0x12ab 0x34cd>
49 elif isinstance(value, list):
50 if len(value) == 0:
51 f.write(indent_str_props + key + ' = "";\n')
52 elif isinstance(value[0], int):
53 list_entries = ' '.join('0x%x' % entry for entry in value)
54 f.write(indent_str_props + key + ' = <%s>;\n' % list_entries)
55 else:
56 list_entries = ', '.join('"%s"' % entry for entry in value)
57 f.write(indent_str_props + key + ' = %s;\n' % list_entries)
58 elif isinstance(value, str):
59 # path: /incbin/("path/to/file")
60 if key in ["data"] and value.startswith('/incbin/('):
61 f.write(indent_str_props + key + ' = %s;\n' % value)
62 # Integers which are already string formatted
63 elif value.startswith("<") and value.endswith(">"):
64 f.write(indent_str_props + key + ' = %s;\n' % value)
65 else:
66 f.write(indent_str_props + key + ' = "%s";\n' % value)
67 else:
68 bb.fatal("%s has unexpexted data type." % str(value))
69 for sub_node in self.sub_nodes:
70 sub_node.emit(f, indent + self.INDENT_SIZE)
71 f.write(indent_str_name + '};\n')
72
73class ItsNodeImages(ItsNode):
74 def __init__(self, parent_node):
75 super().__init__("images", parent_node)
76
77class ItsNodeConfigurations(ItsNode):
78 def __init__(self, parent_node):
79 super().__init__("configurations", parent_node)
80
81class ItsNodeHash(ItsNode):
82 def __init__(self, name, parent_node, algo, opt_props=None):
83 properties = {
84 "algo": algo
85 }
86 if opt_props:
87 properties.update(opt_props)
88 super().__init__(name, parent_node, None, properties)
89
90class ItsImageSignature(ItsNode):
91 def __init__(self, name, parent_node, algo, keyname, opt_props=None):
92 properties = {
93 "algo": algo,
94 "key-name-hint": keyname
95 }
96 if opt_props:
97 properties.update(opt_props)
98 super().__init__(name, parent_node, None, properties)
99
100class ItsNodeImage(ItsNode):
101 def __init__(self, name, parent_node, description, type, compression, sub_nodes=None, opt_props=None):
102 properties = {
103 "description": description,
104 "type": type,
105 "compression": compression,
106 }
107 if opt_props:
108 properties.update(opt_props)
109 super().__init__(name, parent_node, sub_nodes, properties)
110
111class ItsNodeDtb(ItsNodeImage):
112 def __init__(self, name, parent_node, description, type, compression,
113 sub_nodes=None, opt_props=None, compatible=None):
114 super().__init__(name, parent_node, description, type, compression, sub_nodes, opt_props)
115 self.compatible = compatible
116
117class ItsNodeDtbAlias(ItsNode):
118 """Additional Configuration Node for a DTB
119
120 Symlinks pointing to a DTB file are handled by an addtitional
121 configuration node referring to another DTB image node.
122 """
123 def __init__(self, name, alias_name, compatible=None):
124 super().__init__(name, parent_node=None, sub_nodes=None, properties=None)
125 self.alias_name = alias_name
126 self.compatible = compatible
127
128class ItsNodeConfigurationSignature(ItsNode):
129 def __init__(self, name, parent_node, algo, keyname, opt_props=None):
130 properties = {
131 "algo": algo,
132 "key-name-hint": keyname
133 }
134 if opt_props:
135 properties.update(opt_props)
136 super().__init__(name, parent_node, None, properties)
137
138class ItsNodeConfiguration(ItsNode):
139 def __init__(self, name, parent_node, description, sub_nodes=None, opt_props=None):
140 properties = {
141 "description": description,
142 }
143 if opt_props:
144 properties.update(opt_props)
145 super().__init__(name, parent_node, sub_nodes, properties)
146
147class ItsNodeRootKernel(ItsNode):
148 """Create FIT images for the kernel
149
150 Currently only a single kernel (no less or more) can be added to the FIT
151 image along with 0 or more device trees and 0 or 1 ramdisk.
152
153 If a device tree included in the FIT image, the default configuration is the
154 firt DTB. If there is no dtb present than the default configuation the kernel.
155 """
156 def __init__(self, description, address_cells, host_prefix, arch, conf_prefix,
157 sign_enable=False, sign_keydir=None,
158 mkimage=None, mkimage_dtcopts=None,
159 mkimage_sign=None, mkimage_sign_args=None,
160 hash_algo=None, sign_algo=None, pad_algo=None,
161 sign_keyname_conf=None,
162 sign_individual=False, sign_keyname_img=None):
163 props = {
164 "description": description,
165 "#address-cells": f"<{address_cells}>"
166 }
167 super().__init__("/", None, None, props)
168 self.images = ItsNodeImages(self)
169 self.configurations = ItsNodeConfigurations(self)
170
171 self._host_prefix = host_prefix
172 self._arch = arch
173 self._conf_prefix = conf_prefix
174
175 # Signature related properties
176 self._sign_enable = sign_enable
177 self._sign_keydir = sign_keydir
178 self._mkimage = mkimage
179 self._mkimage_dtcopts = mkimage_dtcopts
180 self._mkimage_sign = mkimage_sign
181 self._mkimage_sign_args = mkimage_sign_args
182 self._hash_algo = hash_algo
183 self._sign_algo = sign_algo
184 self._pad_algo = pad_algo
185 self._sign_keyname_conf = sign_keyname_conf
186 self._sign_individual = sign_individual
187 self._sign_keyname_img = sign_keyname_img
188 self._sanitize_sign_config()
189
190 self._dtbs = []
191 self._dtb_alias = []
192 self._kernel = None
193 self._ramdisk = None
194 self._bootscr = None
195 self._setup = None
196
197 def _sanitize_sign_config(self):
198 if self._sign_enable:
199 if not self._hash_algo:
200 bb.fatal("FIT image signing is enabled but no hash algorithm is provided.")
201 if not self._sign_algo:
202 bb.fatal("FIT image signing is enabled but no signature algorithm is provided.")
203 if not self._pad_algo:
204 bb.fatal("FIT image signing is enabled but no padding algorithm is provided.")
205 if not self._sign_keyname_conf:
206 bb.fatal("FIT image signing is enabled but no configuration key name is provided.")
207 if self._sign_individual and not self._sign_keyname_img:
208 bb.fatal("FIT image signing is enabled for individual images but no image key name is provided.")
209
210 def write_its_file(self, itsfile):
211 with open(itsfile, 'w') as f:
212 f.write("/dts-v1/;\n\n")
213 self.emit(f, 0)
214
215 def its_add_node_image(self, image_id, description, image_type, compression, opt_props):
216 image_node = ItsNodeImage(
217 image_id,
218 self.images,
219 description,
220 image_type,
221 compression,
222 opt_props=opt_props
223 )
224 if self._hash_algo:
225 ItsNodeHash(
226 "hash-1",
227 image_node,
228 self._hash_algo
229 )
230 if self._sign_individual:
231 ItsImageSignature(
232 "signature-1",
233 image_node,
234 f"{self._hash_algo},{self._sign_algo}",
235 self._sign_keyname_img
236 )
237 return image_node
238
239 def its_add_node_dtb(self, image_id, description, image_type, compression, opt_props, compatible):
240 dtb_node = ItsNodeDtb(
241 image_id,
242 self.images,
243 description,
244 image_type,
245 compression,
246 opt_props=opt_props,
247 compatible=compatible
248 )
249 if self._hash_algo:
250 ItsNodeHash(
251 "hash-1",
252 dtb_node,
253 self._hash_algo
254 )
255 if self._sign_individual:
256 ItsImageSignature(
257 "signature-1",
258 dtb_node,
259 f"{self._hash_algo},{self._sign_algo}",
260 self._sign_keyname_img
261 )
262 return dtb_node
263
264 def fitimage_emit_section_kernel(self, kernel_id, kernel_path, compression,
265 load, entrypoint, mkimage_kernel_type, entrysymbol=None):
266 """Emit the fitImage ITS kernel section"""
267 if self._kernel:
268 bb.fatal("Kernel section already exists in the ITS file.")
269 if entrysymbol:
270 result = subprocess.run([self._host_prefix + "nm", "vmlinux"], capture_output=True, text=True)
271 for line in result.stdout.splitlines():
272 parts = line.split()
273 if len(parts) == 3 and parts[2] == entrysymbol:
274 entrypoint = "<0x%s>" % parts[0]
275 break
276 kernel_node = self.its_add_node_image(
277 kernel_id,
278 "Linux kernel",
279 mkimage_kernel_type,
280 compression,
281 {
282 "data": '/incbin/("' + kernel_path + '")',
283 "arch": self._arch,
284 "os": "linux",
285 "load": f"<{load}>",
286 "entry": f"<{entrypoint}>"
287 }
288 )
289 self._kernel = kernel_node
290
291 def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None,
292 dtbo_loadaddress=None, add_compatible=False):
293 """Emit the fitImage ITS DTB section"""
294 load=None
295 dtb_ext = os.path.splitext(dtb_path)[1]
296 if dtb_ext == ".dtbo":
297 if dtbo_loadaddress:
298 load = dtbo_loadaddress
299 elif dtb_loadaddress:
300 load = dtb_loadaddress
301
302 opt_props = {
303 "data": '/incbin/("' + dtb_path + '")',
304 "arch": self._arch
305 }
306 if load:
307 opt_props["load"] = f"<{load}>"
308
309 # Preserve the DTB's compatible string to be added to the configuration node
310 compatible = None
311 if add_compatible:
312 compatible = get_compatible_from_dtb(dtb_path)
313
314 dtb_node = self.its_add_node_dtb(
315 "fdt-" + dtb_id,
316 "Flattened Device Tree blob",
317 "flat_dt",
318 "none",
319 opt_props,
320 compatible
321 )
322 self._dtbs.append(dtb_node)
323
324 def fitimage_emit_section_dtb_alias(self, dtb_alias_id, dtb_path, add_compatible=False):
325 """Add a configuration node referring to another DTB"""
326 # Preserve the DTB's compatible string to be added to the configuration node
327 compatible = None
328 if add_compatible:
329 compatible = get_compatible_from_dtb(dtb_path)
330
331 dtb_id = os.path.basename(dtb_path)
332 dtb_alias_node = ItsNodeDtbAlias("fdt-" + dtb_id, dtb_alias_id, compatible)
333 self._dtb_alias.append(dtb_alias_node)
334 bb.warn(f"compatible: {compatible}, dtb_alias_id: {dtb_alias_id}, dtb_id: {dtb_id}, dtb_path: {dtb_path}")
335
336 def fitimage_emit_section_boot_script(self, bootscr_id, bootscr_path):
337 """Emit the fitImage ITS u-boot script section"""
338 if self._bootscr:
339 bb.fatal("U-boot script section already exists in the ITS file.")
340 bootscr_node = self.its_add_node_image(
341 bootscr_id,
342 "U-boot script",
343 "script",
344 "none",
345 {
346 "data": '/incbin/("' + bootscr_path + '")',
347 "arch": self._arch,
348 "type": "script"
349 }
350 )
351 self._bootscr = bootscr_node
352
353 def fitimage_emit_section_setup(self, setup_id, setup_path):
354 """Emit the fitImage ITS setup section"""
355 if self._setup:
356 bb.fatal("Setup section already exists in the ITS file.")
357 load = "<0x00090000>"
358 entry = "<0x00090000>"
359 setup_node = self.its_add_node_image(
360 setup_id,
361 "Linux setup.bin",
362 "x86_setup",
363 "none",
364 {
365 "data": '/incbin/("' + setup_path + '")',
366 "arch": self._arch,
367 "os": "linux",
368 "load": load,
369 "entry": entry
370 }
371 )
372 self._setup = setup_node
373
374 def fitimage_emit_section_ramdisk(self, ramdisk_id, ramdisk_path, description="ramdisk", load=None, entry=None):
375 """Emit the fitImage ITS ramdisk section"""
376 if self._ramdisk:
377 bb.fatal("Ramdisk section already exists in the ITS file.")
378 opt_props = {
379 "data": '/incbin/("' + ramdisk_path + '")',
380 "type": "ramdisk",
381 "arch": self._arch,
382 "os": "linux"
383 }
384 if load:
385 opt_props["load"] = f"<{load}>"
386 if entry:
387 opt_props["entry"] = f"<{entry}>"
388
389 ramdisk_node = self.its_add_node_image(
390 ramdisk_id,
391 description,
392 "ramdisk",
393 "none",
394 opt_props
395 )
396 self._ramdisk = ramdisk_node
397
398 def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None):
399 """Emit the fitImage ITS configuration section"""
400 opt_props = {}
401 conf_desc = []
402 sign_entries = []
403
404 if self._kernel:
405 conf_desc.append("Linux kernel")
406 opt_props["kernel"] = self._kernel.name
407 if self._sign_enable:
408 sign_entries.append("kernel")
409
410 if dtb:
411 conf_desc.append("FDT blob")
412 opt_props["fdt"] = dtb.name
413 if dtb.compatible:
414 opt_props["compatible"] = dtb.compatible
415 if self._sign_enable:
416 sign_entries.append("fdt")
417
418 if self._ramdisk:
419 conf_desc.append("ramdisk")
420 opt_props["ramdisk"] = self._ramdisk.name
421 if self._sign_enable:
422 sign_entries.append("ramdisk")
423
424 if self._bootscr:
425 conf_desc.append("u-boot script")
426 opt_props["bootscr"] = self._bootscr.name
427 if self._sign_enable:
428 sign_entries.append("bootscr")
429
430 if self._setup:
431 conf_desc.append("setup")
432 opt_props["setup"] = self._setup.name
433 if self._sign_enable:
434 sign_entries.append("setup")
435
436 # First added configuration is the default configuration
437 default_flag = "0"
438 if len(self.configurations.sub_nodes) == 0:
439 default_flag = "1"
440
441 conf_node = ItsNodeConfiguration(
442 conf_node_name,
443 self.configurations,
444 f"{default_flag} {', '.join(conf_desc)}",
445 opt_props=opt_props
446 )
447 if self._hash_algo:
448 ItsNodeHash(
449 "hash-1",
450 conf_node,
451 self._hash_algo
452 )
453 if self._sign_enable:
454 ItsNodeConfigurationSignature(
455 "signature-1",
456 conf_node,
457 f"{self._hash_algo},{self._sign_algo}",
458 self._sign_keyname_conf,
459 opt_props={
460 "padding": self._pad_algo,
461 "sign-images": sign_entries
462 }
463 )
464
465 def fitimage_emit_section_config(self, default_dtb_image=None):
466 if self._dtbs:
467 for dtb in self._dtbs:
468 dtb_name = dtb.name
469 if dtb.name.startswith("fdt-"):
470 dtb_name = dtb.name[len("fdt-"):]
471 self._fitimage_emit_one_section_config(self._conf_prefix + dtb_name, dtb)
472 for dtb in self._dtb_alias:
473 self._fitimage_emit_one_section_config(self._conf_prefix + dtb.alias_name, dtb)
474 else:
475 # Currently exactly one kernel is supported.
476 self._fitimage_emit_one_section_config(self._conf_prefix + "1")
477
478 default_conf = self.configurations.sub_nodes[0].name
479 if default_dtb_image and self._dtbs:
480 default_conf = self._conf_prefix + default_dtb_image
481 self.configurations.add_property('default', default_conf)
482
483 def run_mkimage_assemble(self, itsfile, fitfile):
484 cmd = [
485 self._mkimage,
486 '-f', itsfile,
487 fitfile
488 ]
489 if self._mkimage_dtcopts:
490 cmd.insert(1, '-D')
491 cmd.insert(2, self._mkimage_dtcopts)
492 try:
493 subprocess.run(cmd, check=True, capture_output=True)
494 except subprocess.CalledProcessError as e:
495 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)}")
496
497 def run_mkimage_sign(self, fitfile):
498 if not self._sign_enable:
499 bb.debug(1, "FIT image signing is disabled. Skipping signing.")
500 return
501
502 # Some sanity checks because mkimage exits with 0 also without needed keys
503 sign_key_path = os.path.join(self._sign_keydir, self._sign_keyname_conf)
504 if not os.path.exists(sign_key_path + '.key') or not os.path.exists(sign_key_path + '.crt'):
505 bb.fatal("%s.key or .crt does not exist" % sign_key_path)
506 if self._sign_individual:
507 sign_key_img_path = os.path.join(self._sign_keydir, self._sign_keyname_img)
508 if not os.path.exists(sign_key_img_path + '.key') or not os.path.exists(sign_key_img_path + '.crt'):
509 bb.fatal("%s.key or .crt does not exist" % sign_key_img_path)
510
511 cmd = [
512 self._mkimage_sign,
513 '-F',
514 '-k', self._sign_keydir,
515 '-r', fitfile
516 ]
517 if self._mkimage_dtcopts:
518 cmd.extend(['-D', self._mkimage_dtcopts])
519 if self._mkimage_sign_args:
520 cmd.extend(shlex.split(self._mkimage_sign_args))
521 try:
522 subprocess.run(cmd, check=True, capture_output=True)
523 except subprocess.CalledProcessError as e:
524 bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}")
525
526
527def symlink_points_below(file_or_symlink, expected_parent_dir):
528 """returns symlink destination if it points below directory"""
529 file_path = os.path.join(expected_parent_dir, file_or_symlink)
530 if not os.path.islink(file_path):
531 return None
532
533 realpath = os.path.relpath(os.path.realpath(file_path), expected_parent_dir)
534 if realpath.startswith(".."):
535 return None
536
537 return realpath
538
539def get_compatible_from_dtb(dtb_path, fdtget_path="fdtget"):
540 compatible = None
541 cmd = [fdtget_path, "-t", "s", dtb_path, "/", "compatible"]
542 try:
543 ret = subprocess.run(cmd, check=True, capture_output=True, text=True)
544 compatible = ret.stdout.strip().split()
545 except subprocess.CalledProcessError:
546 compatible = None
547 return compatible
diff --git a/meta/recipes-kernel/linux/linux-yocto-fitimage.bb b/meta/recipes-kernel/linux/linux-yocto-fitimage.bb
new file mode 100644
index 0000000000..6ce1960a87
--- /dev/null
+++ b/meta/recipes-kernel/linux/linux-yocto-fitimage.bb
@@ -0,0 +1,13 @@
1SUMMARY = "The Linux kernel as a FIT image (optionally with initramfs)"
2SECTION = "kernel"
3
4# If an initramfs is included in the FIT image more licenses apply.
5# But also the kernel uses more than one license (see Documentation/process/license-rules.rst)
6LICENSE = "GPL-2.0-with-Linux-syscall-note"
7LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-with-Linux-syscall-note;md5=0bad96c422c41c3a94009dcfe1bff992"
8
9inherit linux-kernel-base kernel-fit-image
10
11# Set the version of this recipe to the version of the included kernel
12# (without taking the long way around via PV)
13PKGV = "${@get_kernelversion_file("${STAGING_KERNEL_BUILDDIR}")}"