diff options
author | Lennart Poettering <lennart@poettering.net> | 2022-11-16 14:53:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-16 14:53:42 +0100 |
commit | 45e99be3fb2af4b62eb5153a5f7c80fda90e41f3 (patch) | |
tree | 2854e54cb2ff2e417a2519f0dfe8d8022b7b8b76 | |
parent | Merge pull request #25393 from poettering/tpm2-override (diff) | |
parent | systemd-boot man page: add section for virtual machines (diff) | |
download | systemd-45e99be3fb2af4b62eb5153a5f7c80fda90e41f3.tar.xz systemd-45e99be3fb2af4b62eb5153a5f7c80fda90e41f3.zip |
Merge pull request #24855 from kraxel/qemu
better qemu support (handle direct kernel boot etc).
-rw-r--r-- | man/systemd-boot.xml | 17 | ||||
-rw-r--r-- | src/boot/efi/boot.c | 10 | ||||
-rw-r--r-- | src/boot/efi/meson.build | 1 | ||||
-rw-r--r-- | src/boot/efi/vmm.c | 130 | ||||
-rw-r--r-- | src/boot/efi/vmm.h | 8 |
5 files changed, 165 insertions, 1 deletions
diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 0eee532f90..57b66803fa 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -526,6 +526,23 @@ </refsect1> <refsect1> + <title>Using systemd-boot in virtual machines.</title> + + <para>When using qemu with OVMF (UEFI Firmware for virtual machines) the <option>-kernel</option> switch + works not only for linux kernels, but for any EFI binary, including sd-boot and unified linux + kernels. Example command line for loading sd-boot on x64:</para> + + <para> + <command>qemu-system-x86_64 <replaceable>[ ... ]</replaceable> + -kernel /usr/lib/systemd/boot/efi/systemd-bootx64.efi</command> + </para> + + <para>systemd-boot will detect that it was started directly instead of being loaded from ESP and will + search for the ESP in that case, taking into account boot order information from the hypervisor (if + available).</para> + </refsect1> + + <refsect1> <title>See Also</title> <para> <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 84f4cc11a3..76023b14ca 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -16,6 +16,7 @@ #include "linux.h" #include "measure.h" #include "pe.h" +#include "vmm.h" #include "random-seed.h" #include "secure-boot.h" #include "shim.h" @@ -2646,6 +2647,13 @@ static void config_load_all_entries( config_default_entry_select(config); } +static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE **ret_dir) { + if (is_direct_boot(loaded_image->DeviceHandle)) + return vmm_open(&loaded_image->DeviceHandle, ret_dir); + else + return open_volume(loaded_image->DeviceHandle, ret_dir); +} + EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { EFI_LOADED_IMAGE_PROTOCOL *loaded_image; _cleanup_(file_closep) EFI_FILE *root_dir = NULL; @@ -2682,7 +2690,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { export_variables(loaded_image, loaded_image_path, init_usec); - err = open_volume(loaded_image->DeviceHandle, &root_dir); + err = discover_root_dir(loaded_image, &root_dir); if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Unable to open root directory: %r", err); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 395386d3ed..0de43993a4 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -389,6 +389,7 @@ systemd_boot_sources = files( 'boot.c', 'drivers.c', 'random-seed.c', + 'vmm.c', 'shim.c', 'xbootldr.c', ) diff --git a/src/boot/efi/vmm.c b/src/boot/efi/vmm.c new file mode 100644 index 0000000000..b1bfd778fc --- /dev/null +++ b/src/boot/efi/vmm.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <efi.h> +#include <efilib.h> +#include <stdbool.h> + +#include "drivers.h" +#include "efi-string.h" +#include "string-util-fundamental.h" +#include "util.h" + +#define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \ + { 0x1428f772, 0xb64a, 0x441e, {0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 }} + +#define VMM_BOOT_ORDER_GUID \ + { 0x668f4529, 0x63d0, 0x4bb5, {0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a }} + +/* detect direct boot */ +bool is_direct_boot(EFI_HANDLE device) { + EFI_STATUS err; + VENDOR_DEVICE_PATH *dp; + + err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &dp); + if (err != EFI_SUCCESS) + return false; + + /* 'qemu -kernel systemd-bootx64.efi' */ + if (dp->Header.Type == MEDIA_DEVICE_PATH && + dp->Header.SubType == MEDIA_VENDOR_DP && + memcmp(&dp->Guid, &(EFI_GUID)QEMU_KERNEL_LOADER_FS_MEDIA_GUID, sizeof(EFI_GUID)) == 0) + return true; + + /* loaded from firmware volume (sd-boot added to ovmf) */ + if (dp->Header.Type == MEDIA_DEVICE_PATH && + dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP) + return true; + + return false; +} + +static bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) { + if (!start) + return true; + if (!dp) + return false; + for (;;) { + if (IsDevicePathEnd(start)) + return true; + if (IsDevicePathEnd(dp)) + return false; + size_t l1 = DevicePathNodeLength(start); + size_t l2 = DevicePathNodeLength(dp); + if (l1 != l2) + return false; + if (memcmp(dp, start, l1) != 0) + return false; + start = NextDevicePathNode(start); + dp = NextDevicePathNode(dp); + } +} + +/* + * Try find ESP when not loaded from ESP + * + * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are + * present they are used to inspect the filesystems in the specified order. When nothing was found or the + * variables are not present the function will do one final search pass over all filesystems. + * + * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu + * command line) in VMMBootOrderNNNN. The variables contain a device path. + * + * Example qemu command line: + * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1 + * + * Resulting variable: + * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0) + */ +EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles; + EFI_STATUS err, dp_err; + + assert(ret_vmm_dev); + assert(ret_vmm_dir); + + /* find all file system handles */ + err = BS->LocateHandleBuffer(ByProtocol, &FileSystemProtocol, NULL, &n_handles, &handles); + if (err != EFI_SUCCESS) + return err; + + for (size_t order = 0;; order++) { + _cleanup_free_ EFI_DEVICE_PATH *dp = NULL; + char16_t order_str[STRLEN("VMMBootOrder") + 4 + 1]; + + SPrint(order_str, sizeof(order_str), u"VMMBootOrder%04x", order); + dp_err = efivar_get_raw(&(EFI_GUID)VMM_BOOT_ORDER_GUID, order_str, (char**)&dp, NULL); + + for (size_t i = 0; i < n_handles; i++) { + _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL; + EFI_DEVICE_PATH *fs; + + err = BS->HandleProtocol(handles[i], &DevicePathProtocol, (void **) &fs); + if (err != EFI_SUCCESS) + return err; + + /* check against VMMBootOrderNNNN (if set) */ + if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp)) + continue; + + err = open_volume(handles[i], &root_dir); + if (err != EFI_SUCCESS) + continue; + + /* simple ESP check */ + err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI", + EFI_FILE_MODE_READ, + EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY); + if (err != EFI_SUCCESS) + continue; + + *ret_vmm_dev = handles[i]; + *ret_vmm_dir = TAKE_PTR(root_dir); + return EFI_SUCCESS; + } + + if (dp_err != EFI_SUCCESS) + return EFI_NOT_FOUND; + } + assert_not_reached(); +} diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h new file mode 100644 index 0000000000..7bac1a324a --- /dev/null +++ b/src/boot/efi/vmm.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <efi.h> +#include <efilib.h> + +bool is_direct_boot(EFI_HANDLE device); +EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir); |