meta-intel/classes/uefi-comboapp.bbclass
Patrick Ohly 21086869be uefi-comboapp.bbclass: support multiple UEFI combo apps + fixes
The original code in intel-iot-refkit allows to create more than one
UEFI combo app and uses that to create one for removable media and one
for fixed media (after installation), with different boot=PARTUUID=xxx
parameters. This way, an installed image never ended up booting from
the install media.

uefi-comboapp.bbclass now supports the same feature, with
create_uefiapp() as the API function that can be used to create
additional UEFI apps and create_uefiapps as the method where the call
can be added.

In addition, several shortcomings are getting addressed:
- A UEFI combo app must be stored under a name that is specific
  to the image for which it gets created, otherwise different
  image recipes end up overwriting (or using) files from other
  images.
- Signing must be done after creating the apps and before deploying
  them, otherwise the unsigned apps get copied to the image when
  using do_uefiapp_deploy.
- The common code for deployment is now in uefiapp_deploy_at.
- $dest is used instead of ${DEST} because the latter might get
  expanded by bitbake.
- Because do_uefiapp always had to run anew to produce the
  clean, unsigned input for do_uefiapp_sign, having two different
  tasks just added unnecessary complexity. Now all code is in
  do_uefiapp.
- Old files matching the output pattern get removed explicitly,
  because they might not get overwritten when the optional
  app suffix changes between builds, or when the task fails
  in the middle.

Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
2017-07-19 08:30:08 -07:00

181 lines
6.6 KiB
Plaintext

# This class brings a more generic version of the UEFI combo app from refkit to meta-intel.
# It uses a combo file, containing kernel, initramfs and
# command line, presented to the BIOS as UEFI application, by prepending
# it with the efi stub obtained from systemd-boot.
# Don't add syslinux or build an ISO
PCBIOS_forcevariable = "0"
NOISO_forcevariable = "1"
# image-live.bbclass will default INITRD_LIVE to the image INITRD_IMAGE creates.
# We want behavior to be consistent whether or not "live" is in IMAGE_FSTYPES, so
# we default INITRD_LIVE to the INITRD_IMAGE as well.
INITRD_IMAGE ?= "core-image-minimal-initramfs"
INITRD_LIVE ?= " ${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE', expand=True) + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE', True) else ''}"
do_uefiapp[depends] += " \
intel-microcode:do_deploy \
systemd-boot:do_deploy \
virtual/kernel:do_deploy \
"
# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
do_uefiapp[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_IMAGE') else ''}"
# The image does without traditional bootloader.
# In its place, instead, it uses a single UEFI executable binary, which is
# composed by:
# - an 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.
# - the kernel
# - an initramfs (optional)
def create_uefiapp(d, uuid=None, app_suffix=''):
import glob, re
from subprocess import check_call
build_dir = d.getVar('B')
deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
image_link_name = d.getVar('IMAGE_LINK_NAME')
cmdline = '%s/cmdline.txt' % build_dir
linux = '%s/%s' % (deploy_dir_image, d.getVar('KERNEL_IMAGETYPE'))
initrd = '%s/initrd' % build_dir
stub_path = '%s/linux*.efi.stub' % deploy_dir_image
stub = glob.glob(stub_path)[0]
m = re.match(r"\S*(ia32|x64)(.efi)\S*", os.path.basename(stub))
app = "boot%s%s%s" % (m.group(1), app_suffix, m.group(2))
executable = '%s/%s.%s' % (deploy_dir_image, image_link_name, app)
if d.getVar('INITRD_LIVE'):
with open(initrd, 'wb') as dst:
for cpio in d.getVar('INITRD_LIVE').split():
with open(cpio, 'rb') as src:
dst.write(src.read())
initrd_cmd = "--add-section .initrd=%s --change-section-vma .initrd=0x3000000 " % initrd
else:
initrd_cmd = ""
root = 'root=PARTUUID=%s' % uuid if uuid else ''
with open(cmdline, 'w') as f:
f.write('%s %s' % (d.getVar('APPEND'), root))
objcopy_cmd = ("objcopy "
"--add-section .cmdline=%s --change-section-vma .cmdline=0x30000 "
"--add-section .linux=%s --change-section-vma .linux=0x40000 "
"%s %s %s") % \
(cmdline, linux, initrd_cmd, stub, executable)
check_call(objcopy_cmd, shell=True)
python create_uefiapps () {
# We must clean up anything that matches the expected output pattern, to ensure that
# the next steps do not accidentally use old files.
import glob
pattern = d.expand('${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.boot*.efi')
for old_efi in glob.glob(pattern):
os.unlink(old_efi)
uuid = d.getVar('DISK_SIGNATURE_UUID')
create_uefiapp(d, uuid=uuid)
}
sign_uefiapps () {
if ${@ bb.utils.contains('IMAGE_FEATURES', 'secureboot', 'true', 'false', d) } &&
[ -f ${UEFIAPP_SIGNING_KEY} ] && [ -f ${UEFIAPP_SIGNING_CERT} ]; then
for i in `find ${DEPLOY_DIR_IMAGE}/ -name '${IMAGE_LINK_NAME}.boot*.efi'`; do
sbsign --key ${UEFIAPP_SIGNING_KEY} --cert ${UEFIAPP_SIGNING_CERT} $i
sbverify --cert ${UEFIAPP_SIGNING_CERT} $i.signed
mv $i.signed $i
done
fi
}
# This is intentionally split into different parts. This way, derived
# classes or images can extend the individual parts. We can also use
# whatever language (shell script or Python) is more suitable.
python do_uefiapp() {
bb.build.exec_func('create_uefiapps', d)
bb.build.exec_func('sign_uefiapps', d)
}
do_uefiapp[vardeps] += "APPEND DISK_SIGNATURE_UUID INITRD_LIVE KERNEL_IMAGETYPE IMAGE_LINK_NAME"
do_uefiapp[depends] += "${@ bb.utils.contains('IMAGE_FEATURES', 'secureboot', 'sbsigntool-native:do_populate_sysroot', '', d) }"
uefiapp_deploy_at() {
dest=$1
for i in ${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.boot*.efi; do
target=`basename $i`
target=`echo $target | sed -e 's/${IMAGE_LINK_NAME}.//'`
cp --preserve=timestamps -r $i $dest/$target
done
}
do_uefiapp_deploy() {
rm -rf ${IMAGE_ROOTFS}/boot/*
dest=${IMAGE_ROOTFS}/boot/EFI/BOOT
mkdir -p $dest
uefiapp_deploy_at $dest
}
do_uefiapp_deploy[depends] += "${PN}:do_uefiapp"
# This decides when/how we add our tasks to the image
python () {
import os
import hashlib
secureboot = bb.utils.contains('IMAGE_FEATURES', 'secureboot', True, False, d)
# Ensure that if the signing key or cert change, we rerun the uefiapp process
if secureboot:
for varname in ('UEFIAPP_SIGNING_CERT', 'UEFIAPP_SIGNING_KEY'):
filename = d.getVar(varname)
if filename is None:
bb.fatal('%s is not set.' % varname)
if not os.path.isfile(filename):
bb.fatal('%s=%s is not a file.' % (varname, filename))
with open(filename, 'rb') as f:
data = f.read()
hash = hashlib.sha256(data).hexdigest()
d.setVar('%s_HASH' % varname, hash)
# Must reparse and thus rehash on file changes.
bb.parse.mark_dependency(d, filename)
image_fstypes = d.getVar('IMAGE_FSTYPES', True)
initramfs_fstypes = d.getVar('INITRAMFS_FSTYPES', True)
# Don't add any of these tasks to initramfs images
if initramfs_fstypes not in image_fstypes:
bb.build.addtask('uefiapp', 'do_image', 'do_rootfs', d)
bb.build.addtask('uefiapp_deploy', 'do_image', 'do_rootfs', d)
}
do_uefiapp[vardeps] += "UEFIAPP_SIGNING_CERT_HASH UEFIAPP_SIGNING_KEY_HASH"
# Legacy hddimg support below this line
efi_hddimg_populate() {
uefiapp_deploy_at "$1"
}
build_efi_cfg() {
# The command line is built into the combo app, so this is a null op
:
}
populate_kernel_append() {
# The kernel and initrd are built into the app, so we don't need these
if [ -f $dest/initrd ]; then
rm $dest/initrd
fi
if [ -f $dest/vmlinuz ]; then
rm $dest/vmlinuz
fi
}
IMAGE_FEATURES[validitems] += "secureboot"