summaryrefslogtreecommitdiff
path: root/poky/meta/classes-recipe/uki.bbclass
blob: fedff222c6c5dda61e04c578bc7f337a580e1bd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 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 \
    virtual/cross-binutils \
    virtual/kernel \
"

inherit image-artifact-names
require ../conf/image-uefi.conf

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"
# 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 everytime 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
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))

    # 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))
    out, err = bb.process.run(ukify_cmd, shell=True)
    bb.debug(2, "%s\n%s" % (out, err))
}
addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic