uki.bbclass 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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. DEPENDS += "\
  63. os-release \
  64. systemd-boot \
  65. systemd-boot-native \
  66. virtual/cross-binutils \
  67. virtual/kernel \
  68. "
  69. inherit image-artifact-names
  70. require ../conf/image-uefi.conf
  71. INITRAMFS_IMAGE ?= "core-image-minimal-initramfs"
  72. INITRD_ARCHIVE ?= "${INITRAMFS_IMAGE}-${MACHINE}.${INITRAMFS_FSTYPES}"
  73. do_image_complete[depends] += "${INITRAMFS_IMAGE}:do_image_complete"
  74. UKIFY_CMD ?= "ukify build"
  75. UKI_CONFIG_FILE ?= "${UNPACKDIR}/uki.conf"
  76. UKI_FILENAME ?= "uki.efi"
  77. UKI_KERNEL_FILENAME ?= "${KERNEL_IMAGETYPE}"
  78. UKI_CMDLINE ?= "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
  79. # secure boot keys and cert, needs sbsign-tools-native (meta-secure-core)
  80. #UKI_SB_KEY ?= ""
  81. #UKI_SB_CERT ?= ""
  82. IMAGE_EFI_BOOT_FILES ?= "${UKI_FILENAME};EFI/Linux/${UKI_FILENAME}"
  83. do_uki[depends] += " \
  84. systemd-boot:do_deploy \
  85. virtual/kernel:do_deploy \
  86. "
  87. do_uki[depends] += "${@ '${INITRAMFS_IMAGE}:do_image_complete' if d.getVar('INITRAMFS_IMAGE') else ''}"
  88. # ensure that the build directory is empty everytime we generate a newly-created uki
  89. do_uki[cleandirs] = "${B}"
  90. # influence the build directory at the start of the builds
  91. do_uki[dirs] = "${B}"
  92. # we want to allow specifying files in SRC_URI, such as for signing the UKI
  93. python () {
  94. d.delVarFlag("do_fetch","noexec")
  95. d.delVarFlag("do_unpack","noexec")
  96. }
  97. # main task
  98. python do_uki() {
  99. import glob
  100. import bb.process
  101. # base ukify command, can be extended if needed
  102. ukify_cmd = d.getVar('UKIFY_CMD')
  103. deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
  104. # architecture
  105. target_arch = d.getVar('EFI_ARCH')
  106. if target_arch:
  107. ukify_cmd += " --efi-arch %s" % (target_arch)
  108. # systemd stubs
  109. stub = "%s/linux%s.efi.stub" % (d.getVar('DEPLOY_DIR_IMAGE'), target_arch)
  110. if not os.path.exists(stub):
  111. bb.fatal(f"ERROR: cannot find {stub}.")
  112. ukify_cmd += " --stub %s" % (stub)
  113. # initrd
  114. initramfs_image = "%s" % (d.getVar('INITRD_ARCHIVE'))
  115. ukify_cmd += " --initrd=%s" % (os.path.join(deploy_dir_image, initramfs_image))
  116. deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
  117. # kernel
  118. kernel_filename = d.getVar('UKI_KERNEL_FILENAME') or None
  119. if kernel_filename:
  120. kernel = "%s/%s" % (deploy_dir_image, kernel_filename)
  121. if not os.path.exists(kernel):
  122. bb.fatal(f"ERROR: cannot find %s" % (kernel))
  123. ukify_cmd += " --linux=%s" % (kernel)
  124. # not always needed, ukify can detect version from kernel binary
  125. kernel_version = d.getVar('KERNEL_VERSION')
  126. if kernel_version:
  127. ukify_cmd += "--uname %s" % (kernel_version)
  128. else:
  129. bb.fatal("ERROR - UKI_KERNEL_FILENAME not set")
  130. # command line
  131. cmdline = d.getVar('UKI_CMDLINE')
  132. if cmdline:
  133. ukify_cmd += " --cmdline='%s'" % (cmdline)
  134. # dtb
  135. if d.getVar('KERNEL_DEVICETREE'):
  136. for dtb in d.getVar('KERNEL_DEVICETREE').split():
  137. dtb_path = "%s/%s" % (deploy_dir_image, dtb)
  138. if not os.path.exists(dtb_path):
  139. bb.fatal(f"ERROR: cannot find {dtb_path}.")
  140. ukify_cmd += " --devicetree %s" % (dtb_path)
  141. # custom config for ukify
  142. if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
  143. ukify_cmd += " --config=%s" % (d.getVar('UKI_CONFIG_FILE'))
  144. # systemd tools
  145. ukify_cmd += " --tools=%s%s/lib/systemd/tools" % \
  146. (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
  147. # version
  148. ukify_cmd += " --os-release=@%s%s/lib/os-release" % \
  149. (d.getVar("RECIPE_SYSROOT"), d.getVar("prefix"))
  150. # TODO: tpm2 measure for secure boot, depends on systemd-native and TPM tooling
  151. # needed in systemd > 254 to fulfill ConditionSecurity=measured-uki
  152. # Requires TPM device on build host, thus not supported at build time.
  153. #ukify_cmd += " --measure"
  154. # securebooot signing, also for kernel
  155. key = d.getVar('UKI_SB_KEY')
  156. if key:
  157. ukify_cmd += " --sign-kernel --secureboot-private-key='%s'" % (key)
  158. cert = d.getVar('UKI_SB_CERT')
  159. if cert:
  160. ukify_cmd += " --secureboot-certificate='%s'" % (cert)
  161. # custom output UKI filename
  162. output = " --output=%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'), d.getVar('UKI_FILENAME'))
  163. ukify_cmd += " %s" % (output)
  164. # Run the ukify command
  165. bb.debug(2, "uki: running command: %s" % (ukify_cmd))
  166. bb.process.run(ukify_cmd, shell=True)
  167. }
  168. addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic