]>
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 | |
b30a43df | 19 | static EFI_LOADED_IMAGE_PROTOCOL *loaded_image_free(EFI_LOADED_IMAGE_PROTOCOL *img) { |
dc467928 MR |
20 | if (!img) |
21 | return NULL; | |
22 | mfree(img->LoadOptions); | |
23 | return mfree(img); | |
24 | } | |
25 | ||
26 | static EFI_STATUS loaded_image_register( | |
07d0fde4 | 27 | const char *cmdline, UINTN cmdline_len, |
70cd15e9 | 28 | const void *linux_buffer, UINTN linux_length, |
dc467928 MR |
29 | EFI_HANDLE *ret_image) { |
30 | ||
b30a43df | 31 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image = NULL; |
dc467928 MR |
32 | EFI_STATUS err; |
33 | ||
34 | assert(cmdline || cmdline_len > 0); | |
35 | assert(linux_buffer && linux_length > 0); | |
36 | assert(ret_image); | |
4287d083 | 37 | |
dc467928 | 38 | /* create and install new LoadedImage Protocol */ |
b30a43df JJ |
39 | loaded_image = xnew(EFI_LOADED_IMAGE_PROTOCOL, 1); |
40 | *loaded_image = (EFI_LOADED_IMAGE_PROTOCOL) { | |
70cd15e9 | 41 | .ImageBase = (void *) linux_buffer, |
dc467928 MR |
42 | .ImageSize = linux_length |
43 | }; | |
0fa2cac4 | 44 | |
4f943415 | 45 | /* if a cmdline is set convert it to UCS2 */ |
dc467928 | 46 | if (cmdline) { |
4f943415 | 47 | loaded_image->LoadOptions = xstra_to_str(cmdline); |
60c2af56 | 48 | loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions); |
dc467928 | 49 | } |
508df915 | 50 | |
dc467928 | 51 | /* install a new LoadedImage protocol. ret_handle is a new image handle */ |
12f32748 | 52 | err = BS->InstallMultipleProtocolInterfaces( |
dc467928 MR |
53 | ret_image, |
54 | &LoadedImageProtocol, loaded_image, | |
55 | NULL); | |
2a5e4fe4 | 56 | if (err != EFI_SUCCESS) |
dc467928 MR |
57 | loaded_image = loaded_image_free(loaded_image); |
58 | ||
59 | return err; | |
60 | } | |
61 | ||
62 | static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) { | |
63 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image; | |
64 | EFI_STATUS err; | |
65 | ||
66 | if (!loaded_image_handle) | |
67 | return EFI_SUCCESS; | |
68 | ||
69 | /* get the LoadedImage protocol that we allocated earlier */ | |
12f32748 | 70 | err = BS->OpenProtocol( |
70cd15e9 | 71 | loaded_image_handle, &LoadedImageProtocol, (void **) &loaded_image, |
dc467928 | 72 | NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); |
2a5e4fe4 | 73 | if (err != EFI_SUCCESS) |
dc467928 MR |
74 | return err; |
75 | ||
76 | /* close the handle */ | |
12f32748 JJ |
77 | (void) BS->CloseProtocol(loaded_image_handle, &LoadedImageProtocol, NULL, NULL); |
78 | err = BS->UninstallMultipleProtocolInterfaces( | |
dc467928 MR |
79 | loaded_image_handle, |
80 | &LoadedImageProtocol, loaded_image, | |
81 | NULL); | |
2a5e4fe4 | 82 | if (err != EFI_SUCCESS) |
dc467928 MR |
83 | return err; |
84 | loaded_image_handle = NULL; | |
85 | loaded_image = loaded_image_free(loaded_image); | |
86 | ||
87 | return EFI_SUCCESS; | |
88 | } | |
89 | ||
dc467928 MR |
90 | static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) { |
91 | (void) loaded_image_unregister(*loaded_image_handle); | |
92 | *loaded_image_handle = NULL; | |
93 | } | |
94 | ||
a6089431 MR |
95 | EFI_STATUS linux_exec( |
96 | EFI_HANDLE image, | |
07d0fde4 | 97 | const char *cmdline, UINTN cmdline_len, |
70cd15e9 JJ |
98 | const void *linux_buffer, UINTN linux_length, |
99 | const void *initrd_buffer, UINTN initrd_length) { | |
0d43ce52 | 100 | |
dc467928 MR |
101 | _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; |
102 | _cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL; | |
db4122d1 | 103 | uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address; |
dc467928 | 104 | EFI_IMAGE_ENTRY_POINT kernel_entry; |
70cd15e9 | 105 | void *new_buffer; |
0fa2cac4 KS |
106 | EFI_STATUS err; |
107 | ||
508df915 | 108 | assert(image); |
a6089431 | 109 | assert(cmdline || cmdline_len == 0); |
dc467928 | 110 | assert(linux_buffer && linux_length > 0); |
a6089431 | 111 | assert(initrd_buffer || initrd_length == 0); |
508df915 | 112 | |
dc467928 MR |
113 | /* get the necessary fields from the PE header */ |
114 | err = pe_alignment_info(linux_buffer, &kernel_entry_address, &kernel_size_of_image, &kernel_alignment); | |
2a5e4fe4 | 115 | if (err != EFI_SUCCESS) |
0fa2cac4 | 116 | return err; |
dc467928 MR |
117 | /* sanity check */ |
118 | assert(kernel_size_of_image >= linux_length); | |
119 | ||
120 | /* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment | |
121 | is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to | |
ba669952 | 122 | be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has |
dc467928 MR |
123 | space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of |
124 | Code, (static) Data and Headers. | |
125 | ||
126 | Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings) | |
127 | if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small. | |
128 | */ | |
129 | /* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */ | |
09173c91 JJ |
130 | _cleanup_pages_ Pages kernel = xmalloc_pages( |
131 | AllocateAnyPages, | |
132 | EfiLoaderCode, | |
133 | EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment), | |
134 | 0); | |
dc467928 | 135 | new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment)); |
bbc1f2ea | 136 | memcpy(new_buffer, linux_buffer, linux_length); |
dc467928 | 137 | /* zero out rest of memory (probably not needed, but BSS section should be 0) */ |
db4122d1 | 138 | memset((uint8_t *)new_buffer + linux_length, 0, kernel_size_of_image - linux_length); |
0fa2cac4 | 139 | |
dc467928 | 140 | /* get the entry point inside the relocated kernel */ |
db4122d1 | 141 | kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *)new_buffer + kernel_entry_address); |
0fa2cac4 | 142 | |
dc467928 MR |
143 | /* register a LoadedImage Protocol in order to pass on the commandline */ |
144 | err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle); | |
2a5e4fe4 | 145 | if (err != EFI_SUCCESS) |
dc467928 | 146 | return err; |
a6089431 | 147 | |
dc467928 | 148 | /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */ |
a6089431 | 149 | err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); |
2a5e4fe4 | 150 | if (err != EFI_SUCCESS) |
a6089431 | 151 | return err; |
dc467928 MR |
152 | |
153 | /* call the kernel */ | |
12f32748 | 154 | return kernel_entry(loaded_image_handle, ST); |
0fa2cac4 | 155 | } |