diff options
-rw-r--r-- | meta/classes-recipe/kernel-fit-extra-artifacts.bbclass | 19 | ||||
-rw-r--r-- | meta/classes-recipe/kernel-fit-image.bbclass | 187 | ||||
-rw-r--r-- | meta/classes/multilib.bbclass | 1 | ||||
-rw-r--r-- | meta/lib/oe/fitimage.py | 547 | ||||
-rw-r--r-- | meta/recipes-kernel/linux/linux-yocto-fitimage.bb | 13 |
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 | |||
10 | inherit kernel-uboot | ||
11 | |||
12 | kernel_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 | |||
2 | inherit kernel-arch kernel-artifact-names uboot-config deploy | ||
3 | require conf/image-fitimage.conf | ||
4 | |||
5 | S = "${WORKDIR}/sources" | ||
6 | UNPACKDIR = "${S}" | ||
7 | |||
8 | PACKAGE_ARCH = "${MACHINE_ARCH}" | ||
9 | |||
10 | DEPENDS += "\ | ||
11 | u-boot-tools-native dtc-native \ | ||
12 | ${@'kernel-signing-keys-native' if d.getVar('FIT_GENERATE_KEYS') == '1' else ''} \ | ||
13 | " | ||
14 | |||
15 | python () { | ||
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 | |||
31 | do_configure[noexec] = "1" | ||
32 | |||
33 | UBOOT_MKIMAGE_KERNEL_TYPE ?= "kernel" | ||
34 | KERNEL_IMAGEDEST ?= "/boot" | ||
35 | |||
36 | python 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 | } | ||
147 | do_compile[depends] += "virtual/kernel:do_deploy" | ||
148 | |||
149 | do_install() { | ||
150 | install -d "${D}/${KERNEL_IMAGEDEST}" | ||
151 | install -m 0644 "${B}/fitImage" "${D}/${KERNEL_IMAGEDEST}/fitImage" | ||
152 | } | ||
153 | |||
154 | FILES:${PN} = "${KERNEL_IMAGEDEST}" | ||
155 | |||
156 | |||
157 | do_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 | } | ||
187 | addtask 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 | |||
8 | import os | ||
9 | import shlex | ||
10 | import subprocess | ||
11 | import bb | ||
12 | |||
13 | from oeqa.utils.commands import runCmd | ||
14 | |||
15 | class 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 | |||
73 | class ItsNodeImages(ItsNode): | ||
74 | def __init__(self, parent_node): | ||
75 | super().__init__("images", parent_node) | ||
76 | |||
77 | class ItsNodeConfigurations(ItsNode): | ||
78 | def __init__(self, parent_node): | ||
79 | super().__init__("configurations", parent_node) | ||
80 | |||
81 | class 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 | |||
90 | class 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 | |||
100 | class 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 | |||
111 | class 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 | |||
117 | class 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 | |||
128 | class 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 | |||
138 | class 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 | |||
147 | class 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 | |||
527 | def 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 | |||
539 | def 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 @@ | |||
1 | SUMMARY = "The Linux kernel as a FIT image (optionally with initramfs)" | ||
2 | SECTION = "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) | ||
6 | LICENSE = "GPL-2.0-with-Linux-syscall-note" | ||
7 | LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-with-Linux-syscall-note;md5=0bad96c422c41c3a94009dcfe1bff992" | ||
8 | |||
9 | inherit 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) | ||
13 | PKGV = "${@get_kernelversion_file("${STAGING_KERNEL_BUILDDIR}")}" | ||