# SPDX-License-Identifier: MIT # # Copyright PHYTEC Messtechnik GmbH # Copyright (C) 2024 Pengutronix, # # Class for creating (signed) FIT images # Description: # # You have to define the 'images' to put in the FIT image in your recipe file # following this example: # # FITIMAGE_IMAGES ?= "kernel fdt fdto setup ramdisk bootscript" # # FITIMAGE_IMAGE_kernel ?= "virtual/kernel" # FITIMAGE_IMAGE_kernel[type] ?= "kernel" # # FITIMAGE_IMAGE_fdt ?= "virtual/dtb" # or "virtual/kernel" # FITIMAGE_IMAGE_fdt[type] ?= "fdt" # #FITIMAGE_IMAGE_fdt[file] ?= "hw-name.dtb" # # FITIMAGE_IMAGE_fdto ?= "virtual/kernel" # FITIMAGE_IMAGE_fdto[type] ?= "fdto" # FITIMAGE_IMAGE_fdto[file] ?= # # Add a devicetree created on-thy-fly of a base dtb and serveral dtbo's # FITIMAGE_IMAGE_fdtapply ?= "virtual/kernel" # FITIMAGE_IMAGE_fdtapply[type] ?= "fdtapply" # FITIMAGE_IMAGE_fdtapply[file] ?= "base.dtb overlay-1.dtbo overlay-2.dtbo" # FITIMAGE_IMAGE_fdtapply[name] ?= "" # # FITIMAGE_IMAGE_ramdisk ?= "core-image-minimal" # FITIMAGE_IMAGE_ramdisk[type] ?= "ramdisk" # FITIMAGE_IMAGE_ramdisk[fstype] ?= "cpio.gz" # # FITIMAGE_IMAGE_bootscript ?= "bootscript" # FITIMAGE_IMAGE_bootscript[type] ?= "bootscript" # FITIMAGE_IMAGE_bootscript[file] ?= "boot.scr" # # Valid options for the [type] varflag are: "kernel", "fdt", "fdto", "fdtapply", "ramdisk", "bootscript". # # To enable signing, set # # FITIMAGE_SIGN = "1" # # and configure FITIMAGE_SIGN_KEYDIR (and FITIMAGE_SIGN_KEYNAME) according to # your needs. # # For signing via PKCS#11 URIs provided by the meta-oe signing.bbclass, add: # # inherit signing # # FITIMAGE_SIGNING_KEY_ROLE = "fit" # # do_fitimage:prepend() { # signing_prepare # signing_use_role "${FITIMAGE_SIGNING_KEY_ROLE}" # } # # FITIMAGE_SIGN = "1" # FITIMAGE_MKIMAGE_EXTRA_ARGS = "--engine pkcs11" # FITIMAGE_SIGN_KEYDIR = "${PKCS11_URI}" LICENSE ?= "MIT" inherit deploy kernel-artifact-names image-artifact-names kernel-arch nopackages do_patch[noexec] = "1" do_compile[noexec] = "1" do_install[noexec] = "1" deltask do_populate_sysroot INHIBIT_DEFAULT_DEPS = "1" DEPENDS = "u-boot-mkimage-native dtc-native" FITIMAGE_SIGN ?= "0" FITIMAGE_SIGN[doc] = "Enable FIT image signing" FITIMAGE_SIGN_KEYDIR ?= "" FITIMAGE_SIGN_KEYDIR[doc] = "Key directory or pkcs#11 URI to use for signing configuration" FITIMAGE_MKIMAGE_EXTRA_ARGS[doc] = "Extra arguemnts to pass to uboot-mkimage call" FITIMAGE_HASH_ALGO ?= "sha256" FITIMAGE_HASH_ALGO[doc] = "Hash algorithm to use" FITIMAGE_ENCRYPT_ALGO ?= "rsa2048" FITIMAGE_ENCRYPT_ALGO[doc] = "Signature algorithm to use" FITIMAGE_CONFIG_PREFIX ?= "conf-" FITIMAGE_CONFIG_PREFIX[doc] = "Prefix to use for FIT configuration node name" FITIMAGE_LOADADDRESS ??= "" FITIMAGE_ENTRYPOINT ??= "" FITIMAGE_DTB_LOADADDRESS ??= "" FITIMAGE_DTB_OVERLAY_LOADADDRESS ??= "" FITIMAGE_RD_LOADADDRESS ??= "" FITIMAGE_RD_ENTRYPOINT ??= "" PACKAGE_ARCH = "${MACHINE_ARCH}" # Create dependency list from images python __anonymous() { for image in (d.getVar('FITIMAGE_IMAGES') or "").split(): imageflags = d.getVarFlags('FITIMAGE_IMAGE_%s' % image, expand=['type', 'depends']) or {} imgtype = imageflags.get('type') if not imgtype: bb.debug(1, "No [type] given for image '%s', defaulting to 'kernel'" % image) imgtype = 'kernel' recipe = d.getVar('FITIMAGE_IMAGE_%s' % image) if not recipe: bb.error("No recipe set for image '%s'. Specify via 'FITIMAGE_IMAGE_%s = \"\"'" % (recipe, image)) return d.appendVarFlag('do_unpack', 'vardeps', ' FITIMAGE_IMAGE_%s' % image) depends = imageflags.get('depends') if depends: d.appendVarFlag('do_unpack', 'depends', ' ' + depends) continue if imgtype == 'ramdisk': d.appendVarFlag('do_unpack', 'depends', ' ' + recipe + ':do_image_complete') elif 'fdt' in imgtype: d.appendVarFlag('do_unpack', 'depends', ' ' + recipe + ':do_populate_sysroot') d.appendVarFlag('do_unpack', 'depends', ' ' + recipe + ':do_deploy') else: d.appendVarFlag('do_unpack', 'depends', ' ' + recipe + ':do_deploy') if 'fdt' in imgtype and d.getVar('PREFERRED_PROVIDER_virtual/dtb'): d.setVar('EXTERNAL_KERNEL_DEVICETREE', '${RECIPE_SYSROOT}/boot/devicetree') } S = "${WORKDIR}/sources" UNPACKDIR = "${S}" B = "${WORKDIR}/build" # # Emit the fitImage ITS header # def fitimage_emit_fit_header(d, fd): fd.write('/dts-v1/;\n\n/ {\n') fd.write(d.expand('\tdescription = "fitImage for ${DISTRO_NAME}/${PV}/${MACHINE}";\n')) fd.write('\t#address-cells = <1>;\n') # # Emit the fitImage ITS footer # def fitimage_emit_fit_footer(d, fd): fd.write('};\n') # # Emit the fitImage section # def fitimage_emit_section_start(d, fd, section): fd.write(f'\t{section} {{\n') # # Emit the fitImage section end # def fitimage_emit_section_end(d, fd): fd.write('\t};\n') def fitimage_emit_section_kernel(d, fd, imgpath, imgsource, imgcomp): kernelcount = 1 kernel_csum = d.getVar("FITIMAGE_HASH_ALGO") arch = d.getVar("ARCH") loadaddr = d.getVar("FITIMAGE_LOADADDRESS") entryaddr = d.getVar("FITIMAGE_ENTRYPOINT") bb.note(f"Adding kernel-{kernelcount} section to ITS file") fd.write(f'\t\tkernel-{kernelcount} {{\n') fd.write('\t\t\tdescription = "Linux kernel";\n') fd.write(f'\t\t\tdata = /incbin/("{imgpath}/{imgsource}");\n') fd.write('\t\t\ttype = "kernel";\n') fd.write(f'\t\t\tarch = "{arch}";\n') fd.write('\t\t\tos = "linux";\n') fd.write(f'\t\t\tcompression = "{imgcomp}";\n') if (loadaddr): fd.write(f'\t\t\tload = <{loadaddr}>;\n') if (entryaddr): fd.write(f'\t\t\tentry = <{entryaddr}>;\n') fd.write('\t\t\thash-1 {\n') fd.write(f'\t\t\t\talgo = "{kernel_csum}";\n') fd.write('\t\t\t};\n') fd.write('\t\t};\n') # # Emit the fitImage ITS DTB section # def _fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path, loadaddr, desc): dtb_csum = d.getVar("FITIMAGE_HASH_ALGO") arch = d.getVar("ARCH") bb.note(f"Adding fdt-{dtb_file} section to ITS file") fd.write(f'\t\tfdt-{dtb_file} {{\n') fd.write(f'\t\t\tdescription = "{desc}";\n') fd.write(f'\t\t\tdata = /incbin/("{dtb_path}/{dtb_file}");\n') fd.write('\t\t\ttype = "flat_dt";\n') fd.write(f'\t\t\tarch = "{arch}";\n') fd.write('\t\t\tcompression = "none";\n') if loadaddr: fd.write(f'\t\t\tload = <{loadaddr}>;\n') fd.write('\t\t\thash-1 {\n') fd.write(f'\t\t\t\talgo = "{dtb_csum}";\n') fd.write('\t\t\t};\n') fd.write('\t\t};\n') def fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path): loadaddr = d.getVar("FITIMAGE_DTB_LOADADDRESS") _fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path, loadaddr, "Flattened Device Tree blob") # # Emit the fitImage ITS DTB overlay section # def fitimage_emit_section_dtb_overlay(d, fd, dtb_file, dtb_path): loadaddr = d.getVar("FITIMAGE_DTB_OVERLAY_LOADADDRESS") _fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path, loadaddr, "Flattened Device Tree Overlay blob") # # Emit the fitImage ITS ramdisk section # def fitimage_emit_section_ramdisk(d, fd, img_file, img_path): ramdisk_count = "1" ramdisk_csum = d.getVar("FITIMAGE_HASH_ALGO") arch = d.getVar("ARCH") loadaddr = d.getVar("FITIMAGE_RD_LOADADDRESS") entryaddr = d.getVar("FITIMAGE_RD_ENTRYPOINT") bb.note(f"Adding ramdisk-{ramdisk_count} section to ITS file") fd.write(f'\t\tramdisk-{ramdisk_count} {{\n') fd.write(f'\t\t\tdescription = "{img_file}";\n') fd.write(f'\t\t\tdata = /incbin/("{img_path}/{img_file}");\n') fd.write('\t\t\ttype = "ramdisk";\n') fd.write(f'\t\t\tarch = "{arch}";\n') fd.write('\t\t\tos = "linux";\n') fd.write('\t\t\tcompression = "none";\n') if (loadaddr): fd.write(f'\t\t\tload = <{loadaddr}>;\n') if (entryaddr): fd.write(f'\t\t\tentry = <{entryaddr}>;\n') fd.write('\t\t\thash-1 {\n') fd.write(f'\t\t\t\talgo = "{ramdisk_csum}";\n') fd.write('\t\t\t};\n') fd.write('\t\t};\n') def fitimage_emit_section_bootscript(d, fd, imgpath, imgsource): hash_algo = d.getVar("FITIMAGE_HASH_ALGO") arch = d.getVar("ARCH") bb.note(f"Adding bootscr-{imgsource} section to ITS file") fd.write(f'\t\tbootscr-{imgsource} {{\n') fd.write('\t\t\tdescription = "U-boot script";\n') fd.write(f'\t\t\tdata = /incbin/("{imgpath}/{imgsource}");\n') fd.write('\t\t\ttype = "script";\n') fd.write(f'\t\t\tarch = "{arch}";\n') fd.write('\t\t\tos = "linux";\n') fd.write('\t\t\tcompression = "none";\n') fd.write('\t\t\thash-1 {\n') fd.write(f'\t\t\t\talgo = "{hash_algo}";\n') fd.write('\t\t\t};\n') fd.write('\t\t};\n') def fitimage_emit_subsection_signature(d, fd, sign_images_list): hash_algo = d.getVar("FITIMAGE_HASH_ALGO") encrypt_algo = d.getVar("FITIMAGE_ENCRYPT_ALGO") or "" conf_sign_keyname = d.getVar("FITIMAGE_SIGN_KEYNAME") signer_name = d.getVar("FITIMAGE_SIGNER") signer_version = d.getVar("FITIMAGE_SIGNER_VERSION") sign_images = ", ".join(f'"{s}"' for s in sign_images_list) fd.write('\t\t\tsignature-1 {\n') fd.write(f'\t\t\t\talgo = "{hash_algo},{encrypt_algo}";\n') if conf_sign_keyname: fd.write(f'\t\t\t\tkey-name-hint = {conf_sign_keyname}";\n') fd.write(f'\t\t\t\tsign-images = {sign_images};\n') fd.write(f'\t\t\t\tsigner-name = "{signer_name}";\n') fd.write(f'\t\t\t\tsigner-version = "{signer_version}";\n') fd.write('\t\t\t};\n') # # Emit the fitImage ITS configuration section # def fitimage_emit_section_config(d, fd, dtb, kernelcount, ramdiskcount, setupcount, bootscriptid, compatible, dtbcount): sign = d.getVar("FITIMAGE_SIGN") conf_default = None conf_prefix = d.getVar('FITIMAGE_CONFIG_PREFIX', True) or "" bb.note(f"Adding {dtb} section to ITS file") conf_desc="Linux kernel" if dtb: conf_desc += ", FDT blob" if ramdiskcount: conf_desc += ", ramdisk" if setupcount: conf_desc += ", setup" if bootscriptid: conf_desc += ", u-boot script" if dtbcount == 1: conf_default = d.getVar('FITIMAGE_DEFAULT_CONFIG', True) or f'{conf_prefix}{dtb}' if conf_default: fd.write(f'\t\tdefault = "{conf_default}";\n') fd.write(f'\t\t{conf_prefix}{dtb} {{\n') fd.write(f'\t\t\tdescription = "{dtbcount} {conf_desc}";\n') if kernelcount: fd.write('\t\t\tkernel = "kernel-1";\n') fd.write(f'\t\t\tfdt = "fdt-{dtb}";\n') if ramdiskcount: fd.write(f'\t\t\tramdisk = "ramdisk-{ramdiskcount}";\n') if bootscriptid: fd.write(f'\t\t\tbootscr = "bootscr-{bootscriptid}";\n') if compatible: fd.write(f'\t\t\tcompatible = "{compatible}";\n') if sign == "1": sign_images = ["kernel"] if dtb: sign_images.append("fdt") if ramdiskcount: sign_images.append("ramdisk") if setupcount: sign_images.append("setup") if bootscriptid: sign_images.append("bootscr") fitimage_emit_subsection_signature(d, fd, sign_images) fd.write('\t\t' + '};\n') # # Emits a device tree overlay config section # def fitimage_emit_section_config_fdto(d, fd, dtb, compatible): sign = d.getVar("FITIMAGE_SIGN") bb.note("Adding overlay config section to ITS file") fd.write(f'\t\t{dtb} {{\n') fd.write(f'\t\t\tdescription = "Device Tree Overlay";\n') fd.write(f'\t\t\tfdt = "fdt-{dtb}";') if compatible: fd.write(f'\t\t\tcompatible = "{compatible}";') if sign == "1": sign_images = ["fdt"] fitimage_emit_subsection_signature(d, fd, sign_images) fd.write('\t\t' + '};\n') python write_manifest() { machine = d.getVar('MACHINE') kernelcount=1 DTBS = "" DTBOS = "" ramdiskcount = "" setupcount = "" bootscriptid = "" compatible = "" def get_dtbs(d, dtb_suffix): sysroot = d.getVar('RECIPE_SYSROOT') deploydir = d.getVar('DEPLOY_DIR_IMAGE') dtbs = (d.getVar('KERNEL_DEVICETREE') or '').split() dtbs = [os.path.basename(x) for x in dtbs if x.endswith(dtb_suffix)] ext_dtbs = os.listdir(d.getVar('EXTERNAL_KERNEL_DEVICETREE')) if d.getVar('EXTERNAL_KERNEL_DEVICETREE') else [] ext_dtbs = [x for x in ext_dtbs if x.endswith(dtb_suffix)] result = [] # Prefer BSP dts if BSP and kernel provide the same dts for d in sorted(set(dtbs + ext_dtbs)): dtbpath = f'{sysroot}/boot/devicetree/{d}' if d in ext_dtbs else f'{deploydir}/{d}' result.append(dtbpath) return " ".join(result) with open('%s/manifest.its' % d.getVar('B'), 'w') as fd: images = d.getVar('FITIMAGE_IMAGES') if not images: bb.warn("No images specified in FITIMAGE_IMAGES. Generated FIT image will be empty") fitimage_emit_fit_header(d, fd) fitimage_emit_section_start(d, fd, 'images') for image in (images or "").split(): imageflags = d.getVarFlags('FITIMAGE_IMAGE_%s' % image, expand=['file', 'fstype', 'type', 'comp']) or {} imgtype = imageflags.get('type', '') if imgtype == 'kernel': default = "%s-%s%s" % (d.getVar('KERNEL_IMAGETYPE'), machine, d.getVar('KERNEL_IMAGE_BIN_EXT')) imgsource = imageflags.get('file', default) imgcomp = imageflags.get('comp', 'none') imgpath = d.getVar("DEPLOY_DIR_IMAGE") fitimage_emit_section_kernel(d, fd, imgpath, imgsource, imgcomp) elif imgtype == 'fdt': default = get_dtbs(d, "dtb") dtbfiles = imageflags.get('file', default) if not dtbfiles: bb.fatal(f"No dtb file found for image '{image}'. Set KERNEL_DEVICETREE, [file] varflag, or reference devicetree.bbclass-based recipe.") for dtb in dtbfiles.split(): dtb_path, dtb_file = os.path.split(dtb) DTBS += f" {dtb}" fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path) elif imgtype == 'fdto': default = get_dtbs(d, "dtbo") dtbofiles = imageflags.get('file', default) if not dtbofiles: bb.fatal(f"No dtbo file found for image '{image}'. Set KERNEL_DEVICETREE, [file] varflag, or reference devicetree.bbclass-based recipe.") for dtb in dtbofiles.split(): dtb_path, dtb_file = os.path.split(dtb) DTBOS = DTBOS + " " + dtb fitimage_emit_section_dtb_overlay(d, fd, dtb_file, dtb_path) elif imgtype == 'fdtapply': import subprocess dtbofiles = imageflags.get('file', None) if not dtbofiles: bb.fatal(f"No dtbo file found for image '{image}'. Set via [file] varflag.") dtboutname = imageflags.get('name', None) if not dtboutname: bb.fatal(f"No dtb output name found for image '{image}'. Set via [name] varflag.") dtbresult = "%s/%s" % (d.getVar('B'), dtboutname) dtbcommand = "" for dtb in dtbofiles.split(): dtb_path, dtb_file = os.path.split(dtb) if not dtb_path: dtb_path = d.getVar("DEPLOY_DIR_IMAGE") if not dtbcommand: if not dtb_file.endswith('.dtb'): bb.fatal(f"fdtapply failed: Expected (non-overlay) .dtb file as first element, but got {dtb_file}") dtbcommand = f"fdtoverlay -i {dtb_path}/{dtb_file} -o {dtbresult}" else: if not dtb_file.endswith('.dtbo'): bb.fatal(f"fdtapply failed: Expected .dtbo file, but got {dtb_file}") dtbcommand += f" {dtb_path}/{dtb_file}" result = subprocess.run(dtbcommand, stderr=subprocess.PIPE, shell=True, text=True) if result.returncode != 0: bb.fatal(f"Running {dtbcommand} failed: {result.stderr}") dtb_path, dtb_file = os.path.split(dtbresult) DTBS += f" {dtbresult}" fitimage_emit_section_dtb(d, fd, dtb_file, dtb_path) elif imgtype == 'ramdisk': ramdiskcount = "1" default_imgfstype = d.getVar('INITRAMFS_FSTYPES' or "").split()[0] img_fstype = imageflags.get('fstype', default_imgfstype) img_file = "%s%s.%s" % (d.getVar('FITIMAGE_IMAGE_%s' % image), d.getVar('IMAGE_MACHINE_SUFFIX'), img_fstype) img_path = d.getVar("DEPLOY_DIR_IMAGE") fitimage_emit_section_ramdisk(d, fd, img_file, img_path) elif imgtype == 'bootscript': if bootscriptid: bb.fatal("Only a single boot script is supported (already set to: %s)" % bootscriptid) imgsource = imageflags.get('file', None) imgpath = d.getVar("DEPLOY_DIR_IMAGE") bootscriptid = imgsource fitimage_emit_section_bootscript(d, fd, imgpath, imgsource) fitimage_emit_section_end(d, fd) # # Step 5: Prepare a configurations section # fitimage_emit_section_start(d, fd, 'configurations') dtbcount = 1 for dtb in (DTBS or "").split(): import subprocess try: cmd = "fdtget -t s {} / compatible".format(dtb) compatible = subprocess.check_output(cmd, shell=True, text=True).split()[0] except subprocess.CalledProcessError: bb.fatal("Failed to find root-node compatible string in (%s)" % dtb) dtb_path, dtb_file = os.path.split(dtb) fitimage_emit_section_config(d, fd, dtb_file, kernelcount, ramdiskcount, setupcount, bootscriptid, compatible, dtbcount) dtbcount += 1 for dtb in (DTBOS or "").split(): import subprocess try: cmd = "fdtget -t s {} / compatible".format(dtb) compatible = subprocess.check_output(cmd, shell=True, text=True).split()[0] except subprocess.CalledProcessError: bb.note("Failed to find root-node compatible string in (%s)" % dtb) compatible = None dtb_path, dtb_file = os.path.split(dtb) fitimage_emit_section_config_fdto(d, fd, dtb_file, compatible) fitimage_emit_section_end(d, fd) fitimage_emit_fit_footer(d, fd) } do_configure[postfuncs] += "write_manifest" do_fitimage () { if [ "${FITIMAGE_SIGN}" = "1" ]; then uboot-mkimage ${FITIMAGE_MKIMAGE_EXTRA_ARGS} \ -k ${FITIMAGE_SIGN_KEYDIR} -r \ -f "${B}/manifest.its" \ "${B}/fitImage" else uboot-mkimage ${FITIMAGE_MKIMAGE_EXTRA_ARGS} \ -f "${B}/manifest.its" \ "${B}/fitImage" fi } addtask fitimage after do_configure ITS_NAME ?= "${PN}-${KERNEL_ARTIFACT_NAME}" ITS_LINK_NAME ?= "${PN}-${KERNEL_ARTIFACT_LINK_NAME}" FITIMAGE_IMAGE_NAME ?= "fitImage-${PN}-${KERNEL_FIT_NAME}${KERNEL_FIT_BIN_EXT}" FITIMAGE_IMAGE_LINK_NAME ?= "fitImage-${PN}-${KERNEL_FIT_LINK_NAME}" SSTATE_SKIP_CREATION:task-deploy = '1' do_deploy() { bbnote 'Copying fit-image.its source file...' install -m 0644 ${B}/manifest.its ${DEPLOYDIR}/${ITS_NAME}.its bbnote 'Copying all created fdt from type fdtapply' for DTB_FILE in `find ${B} -maxdepth 1 -name *.dtb`; do install -m 0644 ${DTB_FILE} ${DEPLOYDIR}/ done bbnote 'Copying fitImage file...' install -m 0644 ${B}/fitImage ${DEPLOYDIR}/${FITIMAGE_IMAGE_NAME} cd ${DEPLOYDIR} ln -sf ${ITS_NAME}.its ${ITS_LINK_NAME}.its ln -sf ${FITIMAGE_IMAGE_NAME} ${FITIMAGE_IMAGE_LINK_NAME} } addtask deploy after do_fitimage before do_build