Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate UKI file for DTS and iPXE #233

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions meta-dts-distro/classes-recipe/uki.bbclass
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Unified kernel image (UKI) class
#
# This bbclass merges kernel, initrd etc as a UKI standard UEFI binary,
# to be loaded with UEFI firmware and systemd-boot on target HW.
# TPM PCR pre-calculation is not supported since systemd-measure tooling
# is meant to run on target, not in cross compile environment.
#
# See:
# https://www.freedesktop.org/software/systemd/man/latest/ukify.html
# https://uapi-group.org/specifications/specs/unified_kernel_image/
#
# The UKI contains:
#
# - UEFI stub
# The linux kernel can generate a UEFI stub, however the one from systemd-boot can fetch
# the command line from a separate section of the EFI application, avoiding the need to
# rebuild the kernel.
# - kernel
# - initramfs
# - kernel command line
# - uname -r kernel version
# - /etc/os-release to create a boot menu with version details
# - optionally secure boot signature(s)
# - other metadata (e.g. TPM PCR measurements)
#
# Usage instructions:
#
# - requires UEFI compatible firmware on target, e.g. qemuarm64-secureboot u-boot based
# from meta-arm or qemux86 ovmf/edk2 based firmware for x86_64
#
# - Distro/build config:
#
# INIT_MANAGER = "systemd"
# MACHINE_FEATURES:append = " efi"
# EFI_PROVIDER = "systemd-boot"
# INITRAMFS_IMAGE = "core-image-minimal-initramfs"
#
# - image recipe:
#
# inherit uki
#
# - qemuboot/runqemu changes in image recipe or build config:
#
# # Kernel command line must be inside the signed uki
# QB_KERNEL_ROOT = ""
# # kernel is in the uki image, not loaded separately
# QB_DEFAULT_KERNEL = "none"
#
# - for UEFI secure boot, systemd-boot and uki (including kernel) can
# be signed but require sbsign-tool-native (recipe available from meta-secure-core,
# see also qemuarm64-secureboot from meta-arm). Set variable
# UKI_SB_KEY to path of private key and UKI_SB_CERT for certificate.
# Note that systemd-boot also need to be signed with the same key.
#
# - at runtime, UEFI firmware will load and boot systemd-boot which
# creates a menu from all detected uki binaries. No need to manually
# setup boot menu entries.
#
# - see efi-uki-bootdisk.wks.in how to create ESP partition which hosts systemd-boot,
# config file(s) for systemd-boot and the UKI binaries.
#

DEPENDS += "\
os-release \
systemd-boot \
systemd-boot-native \
binutils-cross-${TARGET_ARCH} \
virtual/kernel \
python3-pefile-native \
"

inherit image-artifact-names

INITRAMFS_IMAGE ?= "core-image-minimal-initramfs"

INITRD_ARCHIVE ?= "${INITRAMFS_IMAGE}-${MACHINE}.${INITRAMFS_FSTYPES}"

do_image_complete[depends] += "${INITRAMFS_IMAGE}:do_image_complete"

UKIFY_CMD ?= "ukify build"
UKI_CONFIG_FILE ?= "${UNPACKDIR}/uki.conf"
UKI_FILENAME ?= "uki.efi"
UKI_KERNEL_FILENAME ?= "${KERNEL_IMAGETYPE}"
UKI_CMDLINE ?= "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
# secure boot keys and cert, needs sbsign-tools-native (meta-secure-core)
#UKI_SB_KEY ?= ""
#UKI_SB_CERT ?= ""

IMAGE_EFI_BOOT_FILES ?= "${UKI_FILENAME};EFI/Linux/${UKI_FILENAME}"

do_uki[depends] += " \
systemd-boot:do_deploy \
virtual/kernel:do_deploy \
"
do_uki[depends] += "${@ '${INITRAMFS_IMAGE}:do_image_complete' if d.getVar('INITRAMFS_IMAGE') else ''}"

# ensure that the build directory is empty every time we generate a newly-created uki
do_uki[cleandirs] = "${B}"
# influence the build directory at the start of the builds
do_uki[dirs] = "${B}"

# we want to allow specifying files in SRC_URI, such as for signing the UKI
# nooelint: oelint.task.noanonpython
python () {
d.delVarFlag("do_fetch","noexec")
d.delVarFlag("do_unpack","noexec")
}

# main task
python do_uki() {
import glob
import bb.process

# base ukify command, can be extended if needed
ukify_cmd = d.getVar('UKIFY_CMD')

deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')

# architecture
target_arch = d.getVar('EFI_ARCH')
if target_arch:
ukify_cmd += " --efi-arch %s" % (target_arch)

# systemd stubs
stub = "%s/linux%s.efi.stub" % (d.getVar('DEPLOY_DIR_IMAGE'), target_arch)
if not os.path.exists(stub):
bb.fatal(f"ERROR: cannot find {stub}.")
ukify_cmd += " --stub %s" % (stub)

# initrd
initramfs_image = "%s" % (d.getVar('INITRD_ARCHIVE'))
ukify_cmd += " --initrd=%s" % (os.path.join(deploy_dir_image, initramfs_image))

deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')

# kernel
kernel_filename = d.getVar('UKI_KERNEL_FILENAME') or None
if kernel_filename:
kernel = "%s/%s" % (deploy_dir_image, kernel_filename)
if not os.path.exists(kernel):
bb.fatal(f"ERROR: cannot find %s" % (kernel))
ukify_cmd += " --linux=%s" % (kernel)
# not always needed, ukify can detect version from kernel binary
kernel_version = d.getVar('KERNEL_VERSION')
if kernel_version:
ukify_cmd += "--uname %s" % (kernel_version)
else:
bb.fatal("ERROR - UKI_KERNEL_FILENAME not set")

# command line
cmdline = d.getVar('UKI_CMDLINE')
if cmdline:
ukify_cmd += " --cmdline='%s'" % (cmdline)

# dtb
if d.getVar('KERNEL_DEVICETREE'):
for dtb in d.getVar('KERNEL_DEVICETREE').split():
dtb_path = "%s/%s" % (deploy_dir_image, dtb)
if not os.path.exists(dtb_path):
bb.fatal(f"ERROR: cannot find {dtb_path}.")
ukify_cmd += " --devicetree %s" % (dtb_path)

# custom config for ukify
if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
ukify_cmd += " --config=%s" % (d.getVar('UKI_CONFIG_FILE'))

# systemd tools
ukify_cmd += " --tools=%s%s/lib/systemd/tools" % \
(d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))

# version
ukify_cmd += " --os-release=@%s%s/lib/os-release" % \
(d.getVar("RECIPE_SYSROOT"), d.getVar("prefix"))

# TODO: tpm2 measure for secure boot, depends on systemd-native and TPM tooling
# needed in systemd > 254 to fulfill ConditionSecurity=measured-uki
# Requires TPM device on build host, thus not supported at build time.
#ukify_cmd += " --measure"

# securebooot signing, also for kernel
key = d.getVar('UKI_SB_KEY')
if key:
ukify_cmd += " --sign-kernel --secureboot-private-key='%s'" % (key)
cert = d.getVar('UKI_SB_CERT')
if cert:
ukify_cmd += " --secureboot-certificate='%s'" % (cert)

# custom output UKI filename
output = " --output=%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'), d.getVar('UKI_FILENAME'))
ukify_cmd += " %s" % (output)

# Run the ukify command
bb.debug(2, "uki: running command: %s" % (ukify_cmd))
bb.process.run(ukify_cmd, shell=True)
}
addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic
3 changes: 3 additions & 0 deletions meta-dts-distro/conf/distro/dts-distro.conf
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ IMAGE_EFI_BOOT_FILES += "grub-efi-bootx64.efi;EFI/DTS/grubx64.efi"
PREFERRED_VERSION_flashrom = "dasharo-v1.2.2+git%"
PREFERRED_VERSION_fwupd = "2.0.1"
APPEND += "quiet"

BOOT_LABEL="dts-boot"
ROOT_LABEL="dts-root"
9 changes: 9 additions & 0 deletions meta-dts-distro/recipes-bsp/grub/grub-efi/grub.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
default=DTS
timeout=0

menuentry 'DTS' {
regexp -s 2:rootfs_drive (.*)(hd[0-9]*)(.*) $cmdpath
probe --part-uuid ($rootfs_drive,msdos2) --set=rootfs
chainloader /EFI/DTS/dts.efi root=PARTUUID=$rootfs rootwait console=ttyS0,115200
}
9 changes: 9 additions & 0 deletions meta-dts-distro/recipes-bsp/grub/grub-efi_%.bbappend
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
require ${@bb.utils.contains('DISTRO_FEATURES', 'efi-secure-boot', 'grub-efi-efi-custom.inc', '', d)}

SRC_URI += "file://grub.cfg"

do_deploy:append() {
install -m 644 "${WORKDIR}/grub.cfg" ${DEPLOY_DIR_IMAGE}/
}

GRUB_BUILDIN:append = " probe regexp chain"
27 changes: 27 additions & 0 deletions meta-dts-distro/recipes-dts/images/dts-base-image.bb
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
require dts-base-image.inc
inherit uki

UKI_CMDLINE = "root=LABEL=${ROOT_LABEL} console=${KERNEL_CONSOLE} rootwait ${APPEND}"
UKI_FILENAME = "dts.efi"
IMAGE_EFI_BOOT_FILES += " \
${UKI_FILENAME};EFI/DTS/${UKI_FILENAME} \
grub.cfg;EFI/BOOT/grub.cfg \
"

do_ipxe_uki() {
stub="${DEPLOY_DIR_IMAGE}/linux${EFI_ARCH}.efi.stub"
kernel="${DEPLOY_DIR_IMAGE}/${UKI_KERNEL_FILENAME}"
rootfs="${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.cpio.gz"
ukify build --cmdline "${UKI_CMDLINE}" --stub "${stub}" \
--linux "${kernel}" --initrd "${rootfs}" \
--tools "${RECIPE_SYSROOT_NATIVE}/lib/systemd/tools" \
--os-release "${RECIPE_SYSROOT}/lib/os-release" \
--output "${DEPLOY_DIR_IMAGE}/ipxe_${UKI_FILENAME}"
}

do_ipxe_uki[depends] += " \
systemd-boot:do_deploy \
virtual/kernel:do_deploy \
dts-base-image:do_image_complete \
"

addtask ipxe_uki after do_image_complete before do_build
4 changes: 2 additions & 2 deletions meta-dts-distro/wic/usb-stick-dts.wks.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bootloader --timeout=0 --append=" rootwait"

part /boot --source bootimg-biosplusefi --sourceparams="loader=grub-efi" --ondisk sda --label dts-boot --align 1024 --use-uuid --active --system-id 0xef
part / --source rootfs --fstype=ext4 --ondisk sda --label dts-root --align 1024 --use-uuid --fixed-size 1024
part /boot --source bootimg-biosplusefi --sourceparams="loader=grub-efi" --ondisk sda --label ${BOOT_LABEL} --align 1024 --use-uuid --active --system-id 0xef
part / --source rootfs --fstype=ext4 --ondisk sda --label ${ROOT_LABEL} --align 1024 --use-uuid --fixed-size 1024