226 lines
8.3 KiB
Diff
226 lines
8.3 KiB
Diff
From e2065196eb434008cfcba7c889138f58f7d492d7 Mon Sep 17 00:00:00 2001
|
|
From: Gerd Hoffmann <kraxel@redhat.com>
|
|
Date: Fri, 9 Sep 2022 13:12:04 +0200
|
|
Subject: [PATCH] boot: improve support for qemu
|
|
|
|
systemd-boot expects being loaded from ESP and is quite unhappy in case
|
|
the loaded image device path is something else. When running on qemu
|
|
this can easily happen though. Case one is direct kernel boot, i.e.
|
|
loading via 'qemu -kernel systemd-bootx64.efi'. Case two is sd-boot
|
|
being added to the ovmf firmware image and being loaded from there.
|
|
|
|
This patch detects both cases and goes inspect all file systems known to
|
|
the firmware, trying to find the ESP. When present the
|
|
VMMBootOrderNNNN variables are used to inspect the file systems in the
|
|
given order.
|
|
|
|
(cherry picked from commit 8fec4f95be7a323410f9853b6773c810ba6c7152)
|
|
|
|
Related: #2138081
|
|
---
|
|
src/boot/efi/boot.c | 10 ++-
|
|
src/boot/efi/meson.build | 1 +
|
|
src/boot/efi/vmm.c | 130 +++++++++++++++++++++++++++++++++++++++
|
|
src/boot/efi/vmm.h | 8 +++
|
|
4 files changed, 148 insertions(+), 1 deletion(-)
|
|
create mode 100644 src/boot/efi/vmm.c
|
|
create mode 100644 src/boot/efi/vmm.h
|
|
|
|
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
|
|
index 426bdc3cc2..e182ee7840 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"
|
|
@@ -2639,6 +2640,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;
|
|
@@ -2673,7 +2681,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);
|