]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0fa2cac4 | 2 | |
dc467928 MR |
3 | /* |
4 | * Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes | |
5 | * initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with | |
6 | * EFI LoadedImageProtocol. | |
7 | * | |
8 | * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V. | |
9 | */ | |
10 | ||
0fa2cac4 KS |
11 | #include <efi.h> |
12 | #include <efilib.h> | |
13 | ||
a6089431 | 14 | #include "initrd.h" |
dc467928 MR |
15 | #include "linux.h" |
16 | #include "pe.h" | |
cf0fbc49 | 17 | #include "util.h" |
0fa2cac4 | 18 | |
a529d818 JJ |
19 | #define STUB_PAYLOAD_GUID \ |
20 | { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } } | |
dc467928 | 21 | |
a529d818 JJ |
22 | EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) { |
23 | assert(parent); | |
24 | assert(source); | |
dc467928 | 25 | assert(ret_image); |
4287d083 | 26 | |
a529d818 JJ |
27 | /* We could pass a NULL device path, but it's nicer to provide something. */ |
28 | struct { | |
29 | VENDOR_DEVICE_PATH payload; | |
30 | EFI_DEVICE_PATH end; | |
31 | } _packed_ payload_device_path = { | |
32 | .payload = { | |
33 | .Header = { | |
34 | .Type = MEDIA_DEVICE_PATH, | |
35 | .SubType = MEDIA_VENDOR_DP, | |
36 | .Length = { sizeof(payload_device_path.payload), 0 }, | |
37 | }, | |
38 | .Guid = STUB_PAYLOAD_GUID, | |
39 | }, | |
40 | .end = { | |
41 | .Type = END_DEVICE_PATH_TYPE, | |
42 | .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
43 | .Length = { sizeof(payload_device_path.end), 0 }, | |
44 | }, | |
dc467928 | 45 | }; |
0fa2cac4 | 46 | |
a529d818 JJ |
47 | return BS->LoadImage( |
48 | /*BootPolicy=*/false, | |
49 | parent, | |
50 | &payload_device_path.payload.Header, | |
51 | (void *) source, | |
52 | len, | |
53 | ret_image); | |
dc467928 MR |
54 | } |
55 | ||
a6089431 | 56 | EFI_STATUS linux_exec( |
bfc075fa | 57 | EFI_HANDLE parent, |
07d0fde4 | 58 | const char *cmdline, UINTN cmdline_len, |
70cd15e9 JJ |
59 | const void *linux_buffer, UINTN linux_length, |
60 | const void *initrd_buffer, UINTN initrd_length) { | |
0d43ce52 | 61 | |
dcde6ae1 | 62 | uint32_t compat_address; |
0fa2cac4 KS |
63 | EFI_STATUS err; |
64 | ||
bfc075fa | 65 | assert(parent); |
a6089431 | 66 | assert(cmdline || cmdline_len == 0); |
dc467928 | 67 | assert(linux_buffer && linux_length > 0); |
a6089431 | 68 | assert(initrd_buffer || initrd_length == 0); |
508df915 | 69 | |
dcde6ae1 | 70 | err = pe_kernel_info(linux_buffer, &compat_address); |
ba2a105c JJ |
71 | #if defined(__i386__) || defined(__x86_64__) |
72 | if (err == EFI_UNSUPPORTED) | |
73 | /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover | |
74 | * protocol. */ | |
75 | return linux_exec_efi_handover( | |
bfc075fa | 76 | parent, |
ba2a105c JJ |
77 | cmdline, |
78 | cmdline_len, | |
79 | linux_buffer, | |
80 | linux_length, | |
81 | initrd_buffer, | |
82 | initrd_length); | |
83 | #endif | |
2a5e4fe4 | 84 | if (err != EFI_SUCCESS) |
a529d818 JJ |
85 | return log_error_status_stall(err, u"Bad kernel image: %r", err); |
86 | ||
87 | _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; | |
88 | err = load_image(parent, linux_buffer, linux_length, &kernel_image); | |
89 | if (err != EFI_SUCCESS) | |
90 | return log_error_status_stall(err, u"Error loading kernel image: %r", err); | |
91 | ||
92 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image; | |
93 | err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image); | |
2a5e4fe4 | 94 | if (err != EFI_SUCCESS) |
a529d818 JJ |
95 | return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err); |
96 | ||
97 | if (cmdline) { | |
98 | loaded_image->LoadOptions = xstra_to_str(cmdline); | |
99 | loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions); | |
100 | } | |
a6089431 | 101 | |
a529d818 | 102 | _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; |
a6089431 | 103 | err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); |
2a5e4fe4 | 104 | if (err != EFI_SUCCESS) |
a529d818 JJ |
105 | return log_error_status_stall(err, u"Error registering initrd: %r", err); |
106 | ||
107 | err = BS->StartImage(kernel_image, NULL, NULL); | |
108 | ||
109 | /* Try calling the kernel compat entry point if one exists. */ | |
dcde6ae1 | 110 | if (err == EFI_UNSUPPORTED && compat_address > 0) { |
a529d818 | 111 | EFI_IMAGE_ENTRY_POINT compat_entry = |
dcde6ae1 | 112 | (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address); |
a529d818 JJ |
113 | err = compat_entry(kernel_image, ST); |
114 | } | |
dc467928 | 115 | |
a529d818 | 116 | return log_error_status_stall(err, u"Error starting kernel image: %r", err); |
0fa2cac4 | 117 | } |