diff options
-rw-r--r-- | meta/classes-recipe/uki.bbclass | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/meta/classes-recipe/uki.bbclass b/meta/classes-recipe/uki.bbclass new file mode 100644 index 0000000000..d4f25c7fd2 --- /dev/null +++ b/meta/classes-recipe/uki.bbclass | |||
@@ -0,0 +1,195 @@ | |||
1 | # Unified kernel image (UKI) class | ||
2 | # | ||
3 | # This bbclass merges kernel, initrd etc as a UKI standard UEFI binary, | ||
4 | # to be loaded with UEFI firmware and systemd-boot on target HW. | ||
5 | # TPM PCR pre-calculation is not supported since systemd-measure tooling | ||
6 | # is meant to run on target, not in cross compile environment. | ||
7 | # | ||
8 | # See: | ||
9 | # https://www.freedesktop.org/software/systemd/man/latest/ukify.html | ||
10 | # https://uapi-group.org/specifications/specs/unified_kernel_image/ | ||
11 | # | ||
12 | # The UKI contains: | ||
13 | # | ||
14 | # - UEFI stub | ||
15 | # The linux kernel can generate a UEFI stub, however the one from systemd-boot can fetch | ||
16 | # the command line from a separate section of the EFI application, avoiding the need to | ||
17 | # rebuild the kernel. | ||
18 | # - kernel | ||
19 | # - initramfs | ||
20 | # - kernel command line | ||
21 | # - uname -r kernel version | ||
22 | # - /etc/os-release to create a boot menu with version details | ||
23 | # - optionally secure boot signature(s) | ||
24 | # - other metadata (e.g. TPM PCR measurements) | ||
25 | # | ||
26 | # Usage instructions: | ||
27 | # | ||
28 | # - requires UEFI compatible firmware on target, e.g. qemuarm64-secureboot u-boot based | ||
29 | # from meta-arm or qemux86 ovmf/edk2 based firmware for x86_64 | ||
30 | # | ||
31 | # - Distro/build config: | ||
32 | # | ||
33 | # INIT_MANAGER = "systemd" | ||
34 | # MACHINE_FEATURES:append = " efi" | ||
35 | # EFI_PROVIDER = "systemd-boot" | ||
36 | # INITRAMFS_IMAGE = "core-image-minimal-initramfs" | ||
37 | # | ||
38 | # - image recipe: | ||
39 | # | ||
40 | # inherit uki | ||
41 | # | ||
42 | # - qemuboot/runqemu changes in image recipe or build config: | ||
43 | # | ||
44 | # # Kernel command line must be inside the signed uki | ||
45 | # QB_KERNEL_ROOT = "" | ||
46 | # # kernel is in the uki image, not loaded separately | ||
47 | # QB_DEFAULT_KERNEL = "none" | ||
48 | # | ||
49 | # - for UEFI secure boot, systemd-boot and uki (including kernel) can | ||
50 | # be signed but require sbsign-tool-native (recipe available from meta-secure-core, | ||
51 | # see also qemuarm64-secureboot from meta-arm). Set variable | ||
52 | # UKI_SB_KEY to path of private key and UKI_SB_CERT for certificate. | ||
53 | # Note that systemd-boot also need to be signed with the same key. | ||
54 | # | ||
55 | # - at runtime, UEFI firmware will load and boot systemd-boot which | ||
56 | # creates a menu from all detected uki binaries. No need to manually | ||
57 | # setup boot menu entries. | ||
58 | # | ||
59 | # - see efi-uki-bootdisk.wks.in how to create ESP partition which hosts systemd-boot, | ||
60 | # config file(s) for systemd-boot and the UKI binaries. | ||
61 | # | ||
62 | |||
63 | DEPENDS += "\ | ||
64 | os-release \ | ||
65 | systemd-boot \ | ||
66 | systemd-boot-native \ | ||
67 | virtual/${TARGET_PREFIX}binutils \ | ||
68 | virtual/kernel \ | ||
69 | " | ||
70 | |||
71 | inherit image-artifact-names | ||
72 | require ../conf/image-uefi.conf | ||
73 | |||
74 | INITRAMFS_IMAGE ?= "core-image-minimal-initramfs" | ||
75 | |||
76 | INITRD_ARCHIVE ?= "${INITRAMFS_IMAGE}-${MACHINE}.${INITRAMFS_FSTYPES}" | ||
77 | |||
78 | do_image_complete[depends] += "${INITRAMFS_IMAGE}:do_image_complete" | ||
79 | |||
80 | UKIFY_CMD ?= "ukify build" | ||
81 | UKI_CONFIG_FILE ?= "${UNPACKDIR}/uki.conf" | ||
82 | UKI_FILENAME ?= "uki.efi" | ||
83 | UKI_KERNEL_FILENAME ?= "${KERNEL_IMAGETYPE}" | ||
84 | UKI_CMDLINE ?= "rootwait root=LABEL=root console=${KERNEL_CONSOLE}" | ||
85 | # secure boot keys and cert, needs sbsign-tools-native (meta-secure-core) | ||
86 | #UKI_SB_KEY ?= "" | ||
87 | #UKI_SB_CERT ?= "" | ||
88 | |||
89 | IMAGE_EFI_BOOT_FILES ?= "${UKI_FILENAME};EFI/Linux/${UKI_FILENAME}" | ||
90 | |||
91 | do_uki[depends] += " \ | ||
92 | systemd-boot:do_deploy \ | ||
93 | virtual/kernel:do_deploy \ | ||
94 | " | ||
95 | do_uki[depends] += "${@ '${INITRAMFS_IMAGE}:do_image_complete' if d.getVar('INITRAMFS_IMAGE') else ''}" | ||
96 | |||
97 | # ensure that the build directory is empty everytime we generate a newly-created uki | ||
98 | do_uki[cleandirs] = "${B}" | ||
99 | # influence the build directory at the start of the builds | ||
100 | do_uki[dirs] = "${B}" | ||
101 | |||
102 | # we want to allow specifying files in SRC_URI, such as for signing the UKI | ||
103 | python () { | ||
104 | d.delVarFlag("do_fetch","noexec") | ||
105 | d.delVarFlag("do_unpack","noexec") | ||
106 | } | ||
107 | |||
108 | # main task | ||
109 | python do_uki() { | ||
110 | import glob | ||
111 | import bb.process | ||
112 | |||
113 | # base ukify command, can be extended if needed | ||
114 | ukify_cmd = d.getVar('UKIFY_CMD') | ||
115 | |||
116 | deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE') | ||
117 | |||
118 | # architecture | ||
119 | target_arch = d.getVar('EFI_ARCH') | ||
120 | if target_arch: | ||
121 | ukify_cmd += " --efi-arch %s" % (target_arch) | ||
122 | |||
123 | # systemd stubs | ||
124 | stub = "%s/linux%s.efi.stub" % (d.getVar('DEPLOY_DIR_IMAGE'), target_arch) | ||
125 | if not os.path.exists(stub): | ||
126 | bb.fatal(f"ERROR: cannot find {stub}.") | ||
127 | ukify_cmd += " --stub %s" % (stub) | ||
128 | |||
129 | # initrd | ||
130 | initramfs_image = "%s" % (d.getVar('INITRD_ARCHIVE')) | ||
131 | ukify_cmd += " --initrd=%s" % (os.path.join(deploy_dir_image, initramfs_image)) | ||
132 | |||
133 | deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE') | ||
134 | |||
135 | # kernel | ||
136 | kernel_filename = d.getVar('UKI_KERNEL_FILENAME') or None | ||
137 | if kernel_filename: | ||
138 | kernel = "%s/%s" % (deploy_dir_image, kernel_filename) | ||
139 | if not os.path.exists(kernel): | ||
140 | bb.fatal(f"ERROR: cannot find %s" % (kernel)) | ||
141 | ukify_cmd += " --linux=%s" % (kernel) | ||
142 | # not always needed, ukify can detect version from kernel binary | ||
143 | kernel_version = d.getVar('KERNEL_VERSION') | ||
144 | if kernel_version: | ||
145 | ukify_cmd += "--uname %s" % (kernel_version) | ||
146 | else: | ||
147 | bb.fatal("ERROR - UKI_KERNEL_FILENAME not set") | ||
148 | |||
149 | # command line | ||
150 | cmdline = d.getVar('UKI_CMDLINE') | ||
151 | if cmdline: | ||
152 | ukify_cmd += " --cmdline='%s'" % (cmdline) | ||
153 | |||
154 | # dtb | ||
155 | if d.getVar('KERNEL_DEVICETREE'): | ||
156 | for dtb in d.getVar('KERNEL_DEVICETREE').split(): | ||
157 | dtb_path = "%s/%s" % (deploy_dir_image, dtb) | ||
158 | if not os.path.exists(dtb_path): | ||
159 | bb.fatal(f"ERROR: cannot find {dtb_path}.") | ||
160 | ukify_cmd += " --devicetree %s" % (dtb_path) | ||
161 | |||
162 | # custom config for ukify | ||
163 | if os.path.exists(d.getVar('UKI_CONFIG_FILE')): | ||
164 | ukify_cmd += " --config=%s" % (d.getVar('UKI_CONFIG_FILE')) | ||
165 | |||
166 | # systemd tools | ||
167 | ukify_cmd += " --tools=%s%s/lib/systemd/tools" % \ | ||
168 | (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix")) | ||
169 | |||
170 | # version | ||
171 | ukify_cmd += " --os-release=@%s%s/lib/os-release" % \ | ||
172 | (d.getVar("RECIPE_SYSROOT"), d.getVar("prefix")) | ||
173 | |||
174 | # TODO: tpm2 measure for secure boot, depends on systemd-native and TPM tooling | ||
175 | # needed in systemd > 254 to fulfill ConditionSecurity=measured-uki | ||
176 | # Requires TPM device on build host, thus not supported at build time. | ||
177 | #ukify_cmd += " --measure" | ||
178 | |||
179 | # securebooot signing, also for kernel | ||
180 | key = d.getVar('UKI_SB_KEY') | ||
181 | if key: | ||
182 | ukify_cmd += " --sign-kernel --secureboot-private-key='%s'" % (key) | ||
183 | cert = d.getVar('UKI_SB_CERT') | ||
184 | if cert: | ||
185 | ukify_cmd += " --secureboot-certificate='%s'" % (cert) | ||
186 | |||
187 | # custom output UKI filename | ||
188 | output = " --output=%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'), d.getVar('UKI_FILENAME')) | ||
189 | ukify_cmd += " %s" % (output) | ||
190 | |||
191 | # Run the ukify command | ||
192 | bb.debug("uki: running command: %s" % (ukify_cmd)) | ||
193 | bb.process.run(ukify_cmd, shell=True) | ||
194 | } | ||
195 | addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic | ||