summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2022-11-16 14:53:42 +0100
committerGitHub <noreply@github.com>2022-11-16 14:53:42 +0100
commit45e99be3fb2af4b62eb5153a5f7c80fda90e41f3 (patch)
tree2854e54cb2ff2e417a2519f0dfe8d8022b7b8b76
parentMerge pull request #25393 from poettering/tpm2-override (diff)
parentsystemd-boot man page: add section for virtual machines (diff)
downloadsystemd-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.xml17
-rw-r--r--src/boot/efi/boot.c10
-rw-r--r--src/boot/efi/meson.build1
-rw-r--r--src/boot/efi/vmm.c130
-rw-r--r--src/boot/efi/vmm.h8
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);