Merge pull request #24855 from kraxel/qemu

better qemu support (handle direct kernel boot etc).
This commit is contained in:
Lennart Poettering
2022-11-16 14:53:42 +01:00
committed by GitHub
5 changed files with 165 additions and 1 deletions
+17
View File
@@ -525,6 +525,23 @@
<filename>/etc/kernel/tries</filename> when a boot loader entry is first created.</para>
</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>
+9 -1
View File
@@ -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);
+1
View File
@@ -389,6 +389,7 @@ systemd_boot_sources = files(
'boot.c',
'drivers.c',
'random-seed.c',
'vmm.c',
'shim.c',
'xbootldr.c',
)
+130
View File
@@ -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();
}
+8
View File
@@ -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);