]>
Commit | Line | Data |
---|---|---|
8fec4f95 GH |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <efi.h> | |
4 | #include <efilib.h> | |
5 | #include <stdbool.h> | |
6 | ||
7 | #include "drivers.h" | |
8 | #include "efi-string.h" | |
9 | #include "string-util-fundamental.h" | |
10 | #include "util.h" | |
11 | ||
12 | #define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \ | |
13 | { 0x1428f772, 0xb64a, 0x441e, {0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 }} | |
14 | ||
15 | #define VMM_BOOT_ORDER_GUID \ | |
16 | { 0x668f4529, 0x63d0, 0x4bb5, {0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a }} | |
17 | ||
18 | /* detect direct boot */ | |
19 | bool is_direct_boot(EFI_HANDLE device) { | |
20 | EFI_STATUS err; | |
21 | VENDOR_DEVICE_PATH *dp; | |
22 | ||
23 | err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &dp); | |
24 | if (err != EFI_SUCCESS) | |
25 | return false; | |
26 | ||
27 | /* 'qemu -kernel systemd-bootx64.efi' */ | |
28 | if (dp->Header.Type == MEDIA_DEVICE_PATH && | |
29 | dp->Header.SubType == MEDIA_VENDOR_DP && | |
30 | memcmp(&dp->Guid, &(EFI_GUID)QEMU_KERNEL_LOADER_FS_MEDIA_GUID, sizeof(EFI_GUID)) == 0) | |
31 | return true; | |
32 | ||
33 | /* loaded from firmware volume (sd-boot added to ovmf) */ | |
34 | if (dp->Header.Type == MEDIA_DEVICE_PATH && | |
35 | dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP) | |
36 | return true; | |
37 | ||
38 | return false; | |
39 | } | |
40 | ||
41 | static bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) { | |
42 | if (!start) | |
43 | return true; | |
44 | if (!dp) | |
45 | return false; | |
46 | for (;;) { | |
47 | if (IsDevicePathEnd(start)) | |
48 | return true; | |
49 | if (IsDevicePathEnd(dp)) | |
50 | return false; | |
51 | size_t l1 = DevicePathNodeLength(start); | |
52 | size_t l2 = DevicePathNodeLength(dp); | |
53 | if (l1 != l2) | |
54 | return false; | |
55 | if (memcmp(dp, start, l1) != 0) | |
56 | return false; | |
57 | start = NextDevicePathNode(start); | |
58 | dp = NextDevicePathNode(dp); | |
59 | } | |
60 | } | |
61 | ||
62 | /* | |
63 | * Try find ESP when not loaded from ESP | |
64 | * | |
65 | * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are | |
66 | * present they are used to inspect the filesystems in the specified order. When nothing was found or the | |
67 | * variables are not present the function will do one final search pass over all filesystems. | |
68 | * | |
69 | * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu | |
70 | * command line) in VMMBootOrderNNNN. The variables contain a device path. | |
71 | * | |
72 | * Example qemu command line: | |
73 | * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1 | |
74 | * | |
75 | * Resulting variable: | |
76 | * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0) | |
77 | */ | |
78 | EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { | |
79 | _cleanup_free_ EFI_HANDLE *handles = NULL; | |
80 | size_t n_handles; | |
81 | EFI_STATUS err, dp_err; | |
82 | ||
83 | assert(ret_vmm_dev); | |
84 | assert(ret_vmm_dir); | |
85 | ||
f6d59e2e JJ |
86 | /* Make sure all file systems have been initialized. Only do this in VMs as this is slow |
87 | * on some real firmwares. */ | |
88 | (void) reconnect_all_drivers(); | |
89 | ||
8fec4f95 GH |
90 | /* find all file system handles */ |
91 | err = BS->LocateHandleBuffer(ByProtocol, &FileSystemProtocol, NULL, &n_handles, &handles); | |
92 | if (err != EFI_SUCCESS) | |
93 | return err; | |
94 | ||
95 | for (size_t order = 0;; order++) { | |
96 | _cleanup_free_ EFI_DEVICE_PATH *dp = NULL; | |
97 | char16_t order_str[STRLEN("VMMBootOrder") + 4 + 1]; | |
98 | ||
99 | SPrint(order_str, sizeof(order_str), u"VMMBootOrder%04x", order); | |
100 | dp_err = efivar_get_raw(&(EFI_GUID)VMM_BOOT_ORDER_GUID, order_str, (char**)&dp, NULL); | |
101 | ||
102 | for (size_t i = 0; i < n_handles; i++) { | |
103 | _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL; | |
104 | EFI_DEVICE_PATH *fs; | |
105 | ||
106 | err = BS->HandleProtocol(handles[i], &DevicePathProtocol, (void **) &fs); | |
107 | if (err != EFI_SUCCESS) | |
108 | return err; | |
109 | ||
110 | /* check against VMMBootOrderNNNN (if set) */ | |
111 | if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp)) | |
112 | continue; | |
113 | ||
114 | err = open_volume(handles[i], &root_dir); | |
115 | if (err != EFI_SUCCESS) | |
116 | continue; | |
117 | ||
118 | /* simple ESP check */ | |
119 | err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI", | |
120 | EFI_FILE_MODE_READ, | |
121 | EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY); | |
122 | if (err != EFI_SUCCESS) | |
123 | continue; | |
124 | ||
125 | *ret_vmm_dev = handles[i]; | |
126 | *ret_vmm_dir = TAKE_PTR(root_dir); | |
127 | return EFI_SUCCESS; | |
128 | } | |
129 | ||
130 | if (dp_err != EFI_SUCCESS) | |
131 | return EFI_NOT_FOUND; | |
132 | } | |
133 | assert_not_reached(); | |
134 | } |